diff --git a/.travis.yml b/.travis.yml index a73f784f7..38045ed07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,12 @@ language: android + jdk: - oraclejdk8 - oraclejdk7 + +env: + - GRADLE_OPTS="-XX:MaxPermSize=256m" + android: components: # Uncomment the lines below if you want to diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c5bc3d5e..6d4d3b66f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ ###Changelog +####Version (next) +* Pebble: support pebble health datalog messages of firmware 3.11 (this adds support for deep sleep!) +* Pebble: try to reconnect on new notifications and phone calls when connection was lost unexpectedly +* Pebble: delay between reconnection attempts (from 1 up to 64 seconds) + +####Version 0.9.3 +* Pebble: Fix Pebble Health activation (was not available in the App Manager) +* Simplify connection state display (only connecting->connected) +* Small improvements to the pairing activity +* Mi Band 1S: Fix for mi band firmware update + ####Version 0.9.2 * Mi Band: Fix update of second (HR) firmware on Mi1S (#234) * Fix ordering issue of device infos being displayed diff --git a/app/build.gradle b/app/build.gradle index 2da01cac0..f01ac6b4f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { targetSdkVersion 23 // note: always bump BOTH versionCode and versionName! - versionName "0.9.2" - versionCode 46 + versionName "0.9.4" + versionCode 48 } buildTypes { release { diff --git a/app/src/main/assets/ic_launcher.svg b/app/src/main/assets/ic_launcher.svg new file mode 100644 index 000000000..03b479948 --- /dev/null +++ b/app/src/main/assets/ic_launcher.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractSettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractSettingsActivity.java index d12fee966..4d3c48a13 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractSettingsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractSettingsActivity.java @@ -33,7 +33,7 @@ public abstract class AbstractSettingsActivity extends PreferenceActivity { } public void updateSummary(Preference preference, Object value) { - String stringValue = value.toString(); + String stringValue = String.valueOf(value); if (preference instanceof ListPreference) { // For list preferences, look up the correct display value in diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java index e2a1d56dc..6b566f3e2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java @@ -80,7 +80,7 @@ public class AppManagerActivity extends Activity { List systemApps = new ArrayList<>(); systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); - if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getFirmwareVersion()))) { + if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getHardwareVersion()))) { systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java index 3d438bcdc..dc7eb2f5f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java @@ -29,9 +29,10 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; -import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -118,20 +119,20 @@ public class DebugActivity extends Activity { incomingCallButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - GBApplication.deviceService().onSetCallState( - editContent.getText().toString(), - null, - ServiceCommand.CALL_INCOMING); + CallSpec callSpec = new CallSpec(); + callSpec.command = CallSpec.CALL_INCOMING; + callSpec.number = editContent.getText().toString(); + GBApplication.deviceService().onSetCallState(callSpec); } }); outgoingCallButton = (Button) findViewById(R.id.outgoingCallButton); outgoingCallButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - GBApplication.deviceService().onSetCallState( - editContent.getText().toString(), - null, - ServiceCommand.CALL_OUTGOING); + CallSpec callSpec = new CallSpec(); + callSpec.command = CallSpec.CALL_OUTGOING; + callSpec.number = editContent.getText().toString(); + GBApplication.deviceService().onSetCallState(callSpec); } }); @@ -139,20 +140,18 @@ public class DebugActivity extends Activity { startCallButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - GBApplication.deviceService().onSetCallState( - null, - null, - ServiceCommand.CALL_START); + CallSpec callSpec = new CallSpec(); + callSpec.command = CallSpec.CALL_START; + GBApplication.deviceService().onSetCallState(callSpec); } }); endCallButton = (Button) findViewById(R.id.endCallButton); endCallButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - GBApplication.deviceService().onSetCallState( - null, - null, - ServiceCommand.CALL_END); + CallSpec callSpec = new CallSpec(); + callSpec.command = CallSpec.CALL_END; + GBApplication.deviceService().onSetCallState(callSpec); } }); @@ -199,10 +198,15 @@ public class DebugActivity extends Activity { setMusicInfoButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - GBApplication.deviceService().onSetMusicInfo( - editContent.getText().toString() + "(artist)", - editContent.getText().toString() + "(album)", - editContent.getText().toString() + "(track)", 20, 10, 2); + MusicSpec musicSpec = new MusicSpec(); + musicSpec.artist = editContent.getText().toString() + "(artist)"; + musicSpec.album = editContent.getText().toString() + "(album)"; + musicSpec.track = editContent.getText().toString() + "(track)"; + musicSpec.duration = 10; + musicSpec.trackCount = 5; + musicSpec.trackNr = 2; + + GBApplication.deviceService().onSetMusicInfo(musicSpec); } }); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java index 0c7b88cdf..7f8d507f1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java @@ -29,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.adapter.ItemWithDetailsAdapter; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -37,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB; public class FwAppInstallerActivity extends Activity implements InstallActivity { private static final Logger LOG = LoggerFactory.getLogger(FwAppInstallerActivity.class); + private static final String ITEM_DETAILS = "details"; private TextView fwAppInstallTextView; private Button installButton; @@ -45,13 +47,22 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity private InstallHandler installHandler; private boolean mayConnect; + private ProgressBar mProgressBar; + private ListView itemListView; + private final List mItems = new ArrayList<>(); + private ItemWithDetailsAdapter mItemAdapter; + + private ListView detailsListView; + private ItemWithDetailsAdapter mDetailsItemAdapter; + private ArrayList mDetails = new ArrayList<>(); + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(GBApplication.ACTION_QUIT)) { + if (GBApplication.ACTION_QUIT.equals(action)) { finish(); - } else if (action.equals(GBDevice.ACTION_DEVICE_CHANGED)) { + } else if (GBDevice.ACTION_DEVICE_CHANGED.equals(action)) { device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE); if (device != null) { refreshBusyState(device); @@ -67,13 +78,13 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity validateInstallation(); } } + } else if (GB.ACTION_DISPLAY_MESSAGE.equals(action)) { + String message = intent.getStringExtra(GB.DISPLAY_MESSAGE_MESSAGE); + int severity = intent.getIntExtra(GB.DISPLAY_MESSAGE_SEVERITY, GB.INFO); + addMessage(message, severity); } } }; - private ProgressBar mProgressBar; - private ListView itemListView; - private final List mItems = new ArrayList<>(); - private ItemWithDetailsAdapter mItemAdapter; private void refreshBusyState(GBDevice dev) { if (dev.isConnecting() || dev.isBusy()) { @@ -107,6 +118,13 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity if (dev != null) { device = dev; } + if (savedInstanceState != null) { + mDetails = savedInstanceState.getParcelableArrayList(ITEM_DETAILS); + if (mDetails == null) { + mDetails = new ArrayList<>(); + } + } + mayConnect = true; itemListView = (ListView) findViewById(R.id.itemListView); mItemAdapter = new ItemWithDetailsAdapter(this, mItems); @@ -114,10 +132,15 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity fwAppInstallTextView = (TextView) findViewById(R.id.infoTextView); installButton = (Button) findViewById(R.id.installButton); mProgressBar = (ProgressBar) findViewById(R.id.installProgressBar); + detailsListView = (ListView) findViewById(R.id.detailsListView); + mDetailsItemAdapter = new ItemWithDetailsAdapter(this, mDetails); + mDetailsItemAdapter.setSize(ItemWithDetailsAdapter.SIZE_SMALL); + detailsListView.setAdapter(mDetailsItemAdapter); setInstallEnabled(false); IntentFilter filter = new IntentFilter(); filter.addAction(GBApplication.ACTION_QUIT); filter.addAction(GBDevice.ACTION_DEVICE_CHANGED); + filter.addAction(GB.ACTION_DISPLAY_MESSAGE); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter); installButton.setOnClickListener(new View.OnClickListener() { @@ -145,6 +168,12 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity } } + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelableArrayList(ITEM_DETAILS, mDetails); + } + private InstallHandler findInstallHandlerFor(Uri uri) { for (DeviceCoordinator coordinator : DeviceHelper.getInstance().getAllCoordinators()) { InstallHandler handler = coordinator.findInstallHandler(uri, this); @@ -195,4 +224,9 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity mItems.add(item); mItemAdapter.notifyDataSetChanged(); } + + private void addMessage(String message, int severity) { + mDetails.add(new GenericItem(message)); + mDetailsItemAdapter.notifyDataSetChanged(); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java index 21f7774f5..f163ba657 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java @@ -4,6 +4,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.FragmentActivity; @@ -79,7 +80,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { } }; private boolean mChartDirty = true; - private boolean supportsHeartrateChart = false; + private boolean supportsHeartrateChart = true; + private AsyncTask refreshTask; public boolean isChartDirty() { return mChartDirty; @@ -119,6 +121,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { protected int AK_LIGHT_SLEEP_COLOR; protected int AK_NOT_WORN_COLOR; + protected String HEARTRATE_LABEL; + protected AbstractChartFragment(String... intentFilterActions) { mIntentFilterActions = new HashSet<>(); if (intentFilterActions != null) { @@ -153,6 +157,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { AK_LIGHT_SLEEP_COLOR = getResources().getColor(R.color.chart_deep_sleep_light); AK_NOT_WORN_COLOR = getResources().getColor(R.color.chart_not_worn_light); + HEARTRATE_LABEL = getContext().getString(R.string.charts_legend_heartrate); + akActivity = new ActivityConfig(ActivityKind.TYPE_ACTIVITY, getString(R.string.abstract_chart_fragment_kind_activity), AK_ACTIVITY_COLOR); akLightSleep = new ActivityConfig(ActivityKind.TYPE_LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR); akDeepSleep = new ActivityConfig(ActivityKind.TYPE_DEEP_SLEEP, getString(R.string.abstract_chart_fragment_kind_deep_sleep), AK_DEEP_SLEEP_COLOR); @@ -363,7 +369,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { if (chartsHost.getDevice() != null) { mChartDirty = false; updateDateInfo(getStartDate(), getEndDate()); - createRefreshTask("Visualizing data", getActivity()).execute(); + if (refreshTask != null && refreshTask.getStatus() != AsyncTask.Status.FINISHED) { + refreshTask.cancel(true); + } + refreshTask = createRefreshTask("Visualizing data", getActivity()).execute(); } } } @@ -445,7 +454,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { colors.add(akActivity.color); } activityEntries.add(createBarEntry(value, i)); - if (hr) { + if (hr && isValidHeartRateValue(sample.getCustomValue())) { heartrateEntries.add(createLineEntry(sample.getCustomValue(), i)); } @@ -488,7 +497,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { barData.setGroupSpace(0); combinedData.setData(barData); - if (hr) { + if (hr && heartrateEntries.size() > 0) { LineDataSet heartrateSet = createHeartrateSet(heartrateEntries, "Heart Rate"); LineData lineData = new LineData(xLabels, heartrateSet); combinedData.setData(lineData); @@ -507,6 +516,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { } } + protected boolean isValidHeartRateValue(int value) { + return value > 0 && value < 255; + } + /** * Implement this to supply the samples to be displayed. * @@ -550,14 +563,19 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { LineDataSet set1 = new LineDataSet(values, label); set1.setColor(HEARTRATE_COLOR); // set1.setColors(colors); -// set1.setDrawCubic(true); -// set1.setCubicIntensity(0.2f); + set1.setDrawCubic(true); + set1.setCubicIntensity(0.1f); // //set1.setDrawFilled(true); // set1.setDrawCircles(false); - set1.setLineWidth(2f); -// set1.setCircleSize(5f); +// set1.setLineWidth(2f); + + set1.setDrawCircles(false); +// set1.setCircleRadius(2f); +// set1.setDrawFilled(true); + + set1.setLineWidth(0.8f); // set1.setFillColor(ColorTemplate.getHoloBlue()); - set1.setDrawValues(false); + set1.setDrawValues(true); // set1.setHighLightColor(Color.rgb(128, 0, 255)); // set1.setColor(Color.rgb(89, 178, 44)); set1.setValueTextColor(CHART_TEXT_COLOR); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java index 608a6f2a9..cd45452de 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java @@ -70,6 +70,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment { // y.setDrawLabels(false); // TODO: make fixed max value optional y.setAxisMaxValue(1f); + y.setAxisMinValue(0); y.setDrawTopYLabelEntry(false); y.setTextColor(CHART_TEXT_COLOR); @@ -82,6 +83,8 @@ public class ActivitySleepChartFragment extends AbstractChartFragment { yAxisRight.setDrawLabels(true); yAxisRight.setDrawTopYLabelEntry(true); yAxisRight.setTextColor(CHART_TEXT_COLOR); + yAxisRight.setAxisMaxValue(250); + yAxisRight.setAxisMinValue(0); // refresh immediately instead of use refreshIfVisible(), for perceived performance refresh(); @@ -125,6 +128,10 @@ public class ActivitySleepChartFragment extends AbstractChartFragment { legendLabels.add(akDeepSleep.label); legendColors.add(akNotWorn.color); legendLabels.add(akNotWorn.label); + if (supportsHeartrate()) { + legendColors.add(HEARTRATE_COLOR); + legendLabels.add(HEARTRATE_LABEL); + } chart.getLegend().setCustom(legendColors, legendLabels); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java index 6b9a75619..b0836eebb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java @@ -151,6 +151,7 @@ public class SleepChartFragment extends AbstractChartFragment { // y.setDrawLabels(false); // TODO: make fixed max value optional y.setAxisMaxValue(1f); + y.setAxisMinValue(0); y.setDrawTopYLabelEntry(false); y.setTextColor(CHART_TEXT_COLOR); @@ -159,10 +160,12 @@ public class SleepChartFragment extends AbstractChartFragment { YAxis yAxisRight = mActivityChart.getAxisRight(); yAxisRight.setDrawGridLines(false); - yAxisRight.setEnabled(false); - yAxisRight.setDrawLabels(false); - yAxisRight.setDrawTopYLabelEntry(false); + yAxisRight.setEnabled(supportsHeartrate()); + yAxisRight.setDrawLabels(true); + yAxisRight.setDrawTopYLabelEntry(true); yAxisRight.setTextColor(CHART_TEXT_COLOR); + yAxisRight.setAxisMaxValue(250); + yAxisRight.setAxisMinValue(0); } protected void setupLegend(Chart chart) { @@ -172,6 +175,10 @@ public class SleepChartFragment extends AbstractChartFragment { legendLabels.add(akLightSleep.label); legendColors.add(akDeepSleep.color); legendLabels.add(akDeepSleep.label); + if (supportsHeartrate()) { + legendColors.add(HEARTRATE_COLOR); + legendLabels.add(HEARTRATE_LABEL); + } chart.getLegend().setCustom(legendColors, legendLabels); chart.getLegend().setTextColor(LEGEND_TEXT_COLOR); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java index 92448d9c0..dcfee994f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java @@ -18,8 +18,12 @@ import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails; */ public class ItemWithDetailsAdapter extends ArrayAdapter { + public static final int SIZE_SMALL = 1; + public static final int SIZE_MEDIUM = 2; + public static final int SIZE_LARGE = 3; private final Context context; private boolean horizontalAlignment; + private int size = SIZE_MEDIUM; public ItemWithDetailsAdapter(Context context, List items) { super(context, 0, items); @@ -42,7 +46,14 @@ public class ItemWithDetailsAdapter extends ArrayAdapter { if (horizontalAlignment) { view = inflater.inflate(R.layout.item_with_details_horizontal, parent, false); } else { - view = inflater.inflate(R.layout.item_with_details, parent, false); + switch (size) { + case SIZE_SMALL: + view = inflater.inflate(R.layout.item_with_details_small, parent, false); + break; + default: + view = inflater.inflate(R.layout.item_with_details, parent, false); + break; + } } } ImageView iconView = (ImageView) view.findViewById(R.id.item_image); @@ -55,4 +66,12 @@ public class ItemWithDetailsAdapter extends ArrayAdapter { return view; } + + public void setSize(int size) { + this.size = size; + } + + public int getSize() { + return size; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java index f8812b308..ce9e1ae46 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java @@ -275,6 +275,24 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl } } + @Override + public void changeStoredSamplesType(int timestampFrom, int timestampTo, int fromKind, int toKind, SampleProvider provider) { + try (SQLiteDatabase db = this.getReadableDatabase()) { + String sql = "UPDATE " + TABLE_GBACTIVITYSAMPLES + " SET " + KEY_TYPE + "= ? WHERE " + + KEY_TYPE + " = ? AND " + + KEY_PROVIDER + " = ? AND " + + KEY_TIMESTAMP + " >= ? AND " + KEY_TIMESTAMP + " < ? ;"; //do not use BETWEEN because the range is inclusive in that case! + + SQLiteStatement statement = db.compileStatement(sql); + statement.bindLong(1, toKind); + statement.bindLong(2, fromKind); + statement.bindLong(3, provider.getID()); + statement.bindLong(4, timestampFrom); + statement.bindLong(5, timestampTo); + statement.execute(); + } + } + @Override public int fetchLatestTimestamp(SampleProvider provider) { try (SQLiteDatabase db = this.getReadableDatabase()) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java index 42aa64de9..49dbbd400 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java @@ -32,6 +32,8 @@ public interface DBHandler { void changeStoredSamplesType(int timestampFrom, int timestampTo, int kind, SampleProvider provider); + void changeStoredSamplesType(int timestampFrom, int timestampTo, int fromKind, int toKind, SampleProvider provider); + int fetchLatestTimestamp(SampleProvider provider); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventDisplayMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventDisplayMessage.java new file mode 100644 index 000000000..29e5ed12f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventDisplayMessage.java @@ -0,0 +1,22 @@ +package nodomain.freeyourgadget.gadgetbridge.deviceevents; + +public class GBDeviceEventDisplayMessage { + public String message; + public int duration; + public int severity; + + /** + * An event for displaying a message to the user. How the message is displayed + * is a detail of the current activity, which needs to listen to the Intent + * GB.ACTION_DISPLAY_MESSAGE. + * + * @param message + * @param duration + * @param severity + */ + public GBDeviceEventDisplayMessage(String message, int duration, int severity) { + this.message = message; + this.duration = duration; + this.severity = severity; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java index f03413afa..5ddad49e3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java @@ -1,14 +1,14 @@ package nodomain.freeyourgadget.gadgetbridge.devices; import android.net.Uri; -import android.support.annotation.Nullable; import java.util.ArrayList; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; -import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; /** * Specifies all events that GadgetBridge intends to send to the gadget device. @@ -22,9 +22,9 @@ public interface EventHandler { void onSetAlarms(ArrayList alarms); - void onSetCallState(@Nullable String number, @Nullable String name, ServiceCommand command); + void onSetCallState(CallSpec callSpec); - void onSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr); + void onSetMusicInfo(MusicSpec musicSpec); void onEnableRealtimeSteps(boolean enable); @@ -48,4 +48,5 @@ public interface EventHandler { void onScreenshotReq(); + void onEnableHeartRateSleepSupport(boolean enable); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/InstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/InstallHandler.java index 1314dc0e0..e83815e62 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/InstallHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/InstallHandler.java @@ -28,7 +28,7 @@ public interface InstallHandler { void validateInstallation(InstallActivity installActivity, GBDevice device); /** - * Allows device specivic code to be execute just before the installation starts + * Allows device specific code to be executed just before the installation starts */ void onStartInstall(GBDevice device); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java index 6f3aa77f3..511de9ddd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java @@ -15,6 +15,7 @@ public final class MiBandConst { public static final String PREF_MIBAND_FITNESS_GOAL = "mi_fitness_goal"; public static final String PREF_MIBAND_DONT_ACK_TRANSFER = "mi_dont_ack_transfer"; public static final String PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR = "mi_reserve_alarm_calendar"; + public static final String PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION = "mi_hr_sleep_detection"; public static final String ORIGIN_SMS = "sms"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java index 3ecf4c655..86ea93303 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java @@ -135,6 +135,11 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator { return location; } + public static boolean getHeartrateSleepSupport(String miBandAddress) throws IllegalArgumentException { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext()); + return prefs.getBoolean(MiBandConst.PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION, false); + } + public static int getFitnessGoal(String miBandAddress) throws IllegalArgumentException { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext()); return Integer.parseInt(prefs.getString(MiBandConst.PREF_MIBAND_FITNESS_GOAL, "10000")); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java index 3cb8343fc..7ef19b80d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java @@ -5,6 +5,7 @@ import android.os.Bundle; import android.preference.Preference; import android.support.v4.content.LocalBroadcastManager; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter; @@ -18,6 +19,7 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PR import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DONT_ACK_TRANSFER; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_FITNESS_GOAL; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR; +import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_WEARSIDE; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_ALIAS; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_COUNT; @@ -43,6 +45,14 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity { }); + final Preference enableHeartrateSleepSupport = findPreference(PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION); + enableHeartrateSleepSupport.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newVal) { + GBApplication.deviceService().onEnableHeartRateSleepSupport(Boolean.TRUE.equals(newVal)); + return true; + } + }); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java index 1b4520303..d910e75eb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java @@ -8,12 +8,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; public class MusicPlaybackReceiver extends BroadcastReceiver { private static final Logger LOG = LoggerFactory.getLogger(MusicPlaybackReceiver.class); - private static String mLastSource; - @Override public void onReceive(Context context, Intent intent) { String artist = intent.getStringExtra("artist"); @@ -24,11 +23,16 @@ public class MusicPlaybackReceiver extends BroadcastReceiver { for (String key : bundle.keySet()) { Object value = bundle.get(key); LOG.info(String.format("%s %s (%s)", key, - value.toString(), value.getClass().getName())); + value != null ? value.toString() : "null", value != null ? value.getClass().getName() : "no class")); } */ LOG.info("Current track: " + artist + ", " + album + ", " + track); - GBApplication.deviceService().onSetMusicInfo(artist, album, track, 0, 0, 0); + MusicSpec musicSpec = new MusicSpec(); + musicSpec.artist = artist; + musicSpec.artist = album; + musicSpec.artist = track; + + GBApplication.deviceService().onSetMusicInfo(musicSpec); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index d4f6ed63d..124c0b99d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -54,6 +54,9 @@ public class NotificationListener extends NotificationListenerService { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); switch (action) { + case GBApplication.ACTION_QUIT: + stopSelf(); + break; case ACTION_MUTE: case ACTION_OPEN: { StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications(); @@ -130,6 +133,7 @@ public class NotificationListener extends NotificationListenerService { public void onCreate() { super.onCreate(); IntentFilter filterLocal = new IntentFilter(); + filterLocal.addAction(GBApplication.ACTION_QUIT); filterLocal.addAction(ACTION_OPEN); filterLocal.addAction(ACTION_DISMISS); filterLocal.addAction(ACTION_DISMISS_ALL); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java index 26513a067..010b5fb82 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java @@ -8,7 +8,7 @@ import android.preference.PreferenceManager; import android.telephony.TelephonyManager; import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; public class PhoneCallReceiver extends BroadcastReceiver { @@ -40,35 +40,38 @@ public class PhoneCallReceiver extends BroadcastReceiver { return; } - ServiceCommand callCommand = null; + int callCommand = CallSpec.CALL_UNDEFINED; switch (state) { case TelephonyManager.CALL_STATE_RINGING: mSavedNumber = number; - callCommand = ServiceCommand.CALL_INCOMING; + callCommand = CallSpec.CALL_INCOMING; break; case TelephonyManager.CALL_STATE_OFFHOOK: if (mLastState == TelephonyManager.CALL_STATE_RINGING) { - callCommand = ServiceCommand.CALL_START; + callCommand = CallSpec.CALL_START; } else { - callCommand = ServiceCommand.CALL_OUTGOING; + callCommand = CallSpec.CALL_OUTGOING; mSavedNumber = number; } break; case TelephonyManager.CALL_STATE_IDLE: if (mLastState == TelephonyManager.CALL_STATE_RINGING) { //missed call would be correct here - callCommand = ServiceCommand.CALL_END; + callCommand = CallSpec.CALL_END; } else { - callCommand = ServiceCommand.CALL_END; + callCommand = CallSpec.CALL_END; } break; } - if (callCommand != null) { + if (callCommand != CallSpec.CALL_UNDEFINED) { SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); if ("never".equals(sharedPrefs.getString("notification_mode_calls", "always"))) { return; } - GBApplication.deviceService().onSetCallState(mSavedNumber, null, callCommand); + CallSpec callSpec = new CallSpec(); + callSpec.number = mSavedNumber; + callSpec.command = callCommand; + GBApplication.deviceService().onSetCallState(callSpec); } mLastState = state; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java index 763e63750..1e46d9782 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java @@ -53,7 +53,6 @@ public class GBDevice implements Parcelable { private BatteryState mBatteryState; private short mRssi = RSSI_UNKNOWN; private String mBusyTask; - private String infoString; private List mDeviceInfos; public GBDevice(String address, String name, DeviceType deviceType) { @@ -204,23 +203,25 @@ public class GBDevice implements Parcelable { } public String getStateString() { + /* + * for simplicity the user wont see all internal states, just connecting -> connected + * instead of connecting->connected->initializing->initialized + */ switch (mState) { case NOT_CONNECTED: return GBApplication.getContext().getString(R.string.not_connected); case WAITING_FOR_RECONNECT: return GBApplication.getContext().getString(R.string.waiting_for_reconnect); case CONNECTING: - return GBApplication.getContext().getString(R.string.connecting); case CONNECTED: - return GBApplication.getContext().getString(R.string.connected); case INITIALIZING: - return GBApplication.getContext().getString(R.string.initializing); + return GBApplication.getContext().getString(R.string.connecting); case AUTHENTICATION_REQUIRED: return GBApplication.getContext().getString(R.string.authentication_required); case AUTHENTICATING: return GBApplication.getContext().getString(R.string.authenticating); case INITIALIZED: - return GBApplication.getContext().getString(R.string.initialized); + return GBApplication.getContext().getString(R.string.connected); } return GBApplication.getContext().getString(R.string.unknown_state); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java index 62d7f15cf..add9f676e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -10,9 +10,10 @@ import java.util.ArrayList; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; -import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; public class GBDeviceService implements DeviceService { @@ -115,23 +116,23 @@ public class GBDeviceService implements DeviceService { } @Override - public void onSetCallState(String number, String name, ServiceCommand command) { + public void onSetCallState(CallSpec callSpec) { // name is actually ignored and provided by the service itself... Intent intent = createIntent().setAction(ACTION_CALLSTATE) - .putExtra(EXTRA_CALL_PHONENUMBER, number) - .putExtra(EXTRA_CALL_COMMAND, command); + .putExtra(EXTRA_CALL_PHONENUMBER, callSpec.number) + .putExtra(EXTRA_CALL_COMMAND, callSpec.command); invokeService(intent); } @Override - public void onSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) { + public void onSetMusicInfo(MusicSpec musicSpec) { Intent intent = createIntent().setAction(ACTION_SETMUSICINFO) - .putExtra(EXTRA_MUSIC_ARTIST, artist) - .putExtra(EXTRA_MUSIC_ALBUM, album) - .putExtra(EXTRA_MUSIC_TRACK, track) - .putExtra(EXTRA_MUSIC_DURATION, duration) - .putExtra(EXTRA_MUSIC_TRACKCOUNT, trackCount) - .putExtra(EXTRA_MUSIC_TRACKNR, trackNr); + .putExtra(EXTRA_MUSIC_ARTIST, musicSpec.artist) + .putExtra(EXTRA_MUSIC_ALBUM, musicSpec.album) + .putExtra(EXTRA_MUSIC_TRACK, musicSpec.track) + .putExtra(EXTRA_MUSIC_DURATION, musicSpec.duration) + .putExtra(EXTRA_MUSIC_TRACKCOUNT, musicSpec.trackCount) + .putExtra(EXTRA_MUSIC_TRACKNR, musicSpec.trackNr); invokeService(intent); } @@ -205,7 +206,14 @@ public class GBDeviceService implements DeviceService { @Override public void onEnableRealtimeSteps(boolean enable) { Intent intent = createIntent().setAction(ACTION_ENABLE_REALTIME_STEPS) - .putExtra(EXTRA_ENABLE_REALTIME_STEPS, enable); + .putExtra(EXTRA_BOOLEAN_ENABLE, enable); + invokeService(intent); + } + + @Override + public void onEnableHeartRateSleepSupport(boolean enable) { + Intent intent = createIntent().setAction(ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT) + .putExtra(EXTRA_BOOLEAN_ENABLE, enable); invokeService(intent); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CallSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CallSpec.java new file mode 100644 index 000000000..1d088a493 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CallSpec.java @@ -0,0 +1,15 @@ +package nodomain.freeyourgadget.gadgetbridge.model; + +public class CallSpec { + public static final int CALL_UNDEFINED = 1; + public static final int CALL_ACCEPT = 1; + public static final int CALL_INCOMING = 2; + public static final int CALL_OUTGOING = 3; + public static final int CALL_REJECT = 4; + public static final int CALL_START = 5; + public static final int CALL_END = 6; + + public String number; + public String name; + public int command; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java index 8217f6d4c..7c38088f4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -33,6 +33,7 @@ public interface DeviceService extends EventHandler { String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms"; String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps"; String ACTION_REALTIME_STEPS = PREFIX + ".action.realtime_steps"; + String ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT = PREFIX + ".action.enable_heartrate_sleep_support"; String EXTRA_DEVICE_ADDRESS = "device_address"; String EXTRA_NOTIFICATION_BODY = "notification_body"; String EXTRA_NOTIFICATION_FLAGS = "notification_flags"; @@ -58,7 +59,7 @@ public interface DeviceService extends EventHandler { String EXTRA_URI = "uri"; String EXTRA_ALARMS = "alarms"; String EXTRA_PERFORM_PAIR = "perform_pair"; - String EXTRA_ENABLE_REALTIME_STEPS = "enable_realtime_steps"; + String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps"; String EXTRA_REALTIME_STEPS = "realtime_steps"; String EXTRA_TIMESTAMP = "timestamp"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java new file mode 100644 index 000000000..af52f02f1 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java @@ -0,0 +1,17 @@ +package nodomain.freeyourgadget.gadgetbridge.model; + +public class MusicSpec { + public static final int MUSIC_UNDEFINED = 0; + public static final int MUSIC_PLAY = 1; + public static final int MUSIC_PAUSE = 2; + public static final int MUSIC_PLAYPAUSE = 3; + public static final int MUSIC_NEXT = 4; + public static final int MUSIC_PREVIOUS = 5; + + public String artist; + public String album; + public String track; + public int duration; + public int trackCount; + public int trackNr; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ServiceCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ServiceCommand.java deleted file mode 100644 index 5cb945119..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ServiceCommand.java +++ /dev/null @@ -1,22 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.model; - -public enum ServiceCommand { - - UNDEFINED, - - CALL_ACCEPT, - CALL_END, - CALL_INCOMING, - CALL_OUTGOING, - CALL_REJECT, - CALL_START, - - MUSIC_PLAY, - MUSIC_PAUSE, - MUSIC_PLAYPAUSE, - MUSIC_NEXT, - MUSIC_PREVIOUS, - - APP_INFO_NAME, - VERSION_FIRMWARE -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java index ad88c1797..d103011ab 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java @@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot; @@ -280,4 +281,14 @@ public abstract class AbstractDeviceSupport implements DeviceSupport { gbDevice.sendDeviceUpdateIntent(context); } + public void handleGBDeviceEvent(GBDeviceEventDisplayMessage message) { + GB.log(message.message, message.severity, null); + + Intent messageIntent = new Intent(GB.ACTION_DISPLAY_MESSAGE); + messageIntent.putExtra(GB.DISPLAY_MESSAGE_MESSAGE, message.message); + messageIntent.putExtra(GB.DISPLAY_MESSAGE_DURATION, message.duration); + messageIntent.putExtra(GB.DISPLAY_MESSAGE_SEVERITY, message.severity); + + LocalBroadcastManager.getInstance(context).sendBroadcast(messageIntent); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index a9261f7ba..6ade401de 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -33,9 +33,10 @@ import nodomain.freeyourgadget.gadgetbridge.externalevents.SMSReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; -import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -44,6 +45,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CA import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DISCONNECT; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_STEPS; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FETCH_ACTIVITY_DATA; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FIND_DEVICE; @@ -63,10 +65,10 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ALA import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_CONFIG; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_START; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_UUID; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_BOOLEAN_ENABLE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_COMMAND; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_PHONENUMBER; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_DEVICE_ADDRESS; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ENABLE_REALTIME_STEPS; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST; @@ -277,26 +279,32 @@ public class DeviceCommunicationService extends Service { break; } case ACTION_CALLSTATE: - ServiceCommand command = (ServiceCommand) intent.getSerializableExtra(EXTRA_CALL_COMMAND); + int command = intent.getIntExtra(EXTRA_CALL_COMMAND, CallSpec.CALL_UNDEFINED); String phoneNumber = intent.getStringExtra(EXTRA_CALL_PHONENUMBER); String callerName = null; if (phoneNumber != null) { callerName = getContactDisplayNameByNumber(phoneNumber); } - mDeviceSupport.onSetCallState(phoneNumber, callerName, command); + + CallSpec callSpec = new CallSpec(); + callSpec.command = command; + callSpec.number = phoneNumber; + callSpec.name = callerName; + mDeviceSupport.onSetCallState(callSpec); break; case ACTION_SETTIME: mDeviceSupport.onSetTime(); break; case ACTION_SETMUSICINFO: - String artist = intent.getStringExtra(EXTRA_MUSIC_ARTIST); - String album = intent.getStringExtra(EXTRA_MUSIC_ALBUM); - String track = intent.getStringExtra(EXTRA_MUSIC_TRACK); - int duration = intent.getIntExtra(EXTRA_MUSIC_DURATION, 0); - int trackCount = intent.getIntExtra(EXTRA_MUSIC_TRACKCOUNT, 0); - int trackNr = intent.getIntExtra(EXTRA_MUSIC_TRACKNR, 0); - mDeviceSupport.onSetMusicInfo(artist, album, track, duration, trackCount, trackNr); + MusicSpec musicSpec = new MusicSpec(); + musicSpec.artist = intent.getStringExtra(EXTRA_MUSIC_ARTIST); + musicSpec.album = intent.getStringExtra(EXTRA_MUSIC_ALBUM); + musicSpec.track = intent.getStringExtra(EXTRA_MUSIC_TRACK); + musicSpec.duration = intent.getIntExtra(EXTRA_MUSIC_DURATION, 0); + musicSpec.trackCount = intent.getIntExtra(EXTRA_MUSIC_TRACKCOUNT, 0); + musicSpec.trackNr = intent.getIntExtra(EXTRA_MUSIC_TRACKNR, 0); + mDeviceSupport.onSetMusicInfo(musicSpec); break; case ACTION_REQUEST_APPINFO: mDeviceSupport.onAppInfoReq(); @@ -331,10 +339,16 @@ public class DeviceCommunicationService extends Service { ArrayList alarms = intent.getParcelableArrayListExtra(EXTRA_ALARMS); mDeviceSupport.onSetAlarms(alarms); break; - case ACTION_ENABLE_REALTIME_STEPS: - boolean enable = intent.getBooleanExtra(EXTRA_ENABLE_REALTIME_STEPS, false); + case ACTION_ENABLE_REALTIME_STEPS: { + boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false); mDeviceSupport.onEnableRealtimeSteps(enable); break; + } + case ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT: { + boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false); + mDeviceSupport.onEnableHeartRateSleepSupport(enable); + break; + } } return START_STICKY; @@ -417,7 +431,10 @@ public class DeviceCommunicationService extends Service { } if (mMusicPlaybackReceiver == null) { mMusicPlaybackReceiver = new MusicPlaybackReceiver(); - registerReceiver(mMusicPlaybackReceiver, new IntentFilter("com.android.music.metachanged")); + IntentFilter filter = new IntentFilter(); + filter.addAction("com.android.music.metachanged"); + //filter.addAction("com.android.music.playstatechanged"); + registerReceiver(mMusicPlaybackReceiver, filter); } if (mTimeChangeReceiver == null) { mTimeChangeReceiver = new TimeChangeReceiver(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java index 993b03190..e54e61bd3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java @@ -13,8 +13,9 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; -import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; /** * Wraps another device support instance and supports busy-checking and throttling of events. @@ -131,19 +132,19 @@ public class ServiceDeviceSupport implements DeviceSupport { // No throttling for the other events @Override - public void onSetCallState(String number, String name, ServiceCommand command) { + public void onSetCallState(CallSpec callSpec) { if (checkBusy("set call state")) { return; } - delegate.onSetCallState(number, name, command); + delegate.onSetCallState(callSpec); } @Override - public void onSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) { + public void onSetMusicInfo(MusicSpec musicSpec) { if (checkBusy("set music info")) { return; } - delegate.onSetMusicInfo(artist, album, track, duration, trackCount, trackNr); + delegate.onSetMusicInfo(musicSpec); } @Override @@ -241,4 +242,12 @@ public class ServiceDeviceSupport implements DeviceSupport { } delegate.onEnableRealtimeSteps(enable); } + + @Override + public void onEnableHeartRateSleepSupport(boolean enable) { + if (checkBusy("enable heartrate sleep support: " + enable)) { + return; + } + delegate.onEnableHeartRateSleepSupport(enable); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java index 051c8ce61..6361431a9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java @@ -16,6 +16,7 @@ import java.util.Set; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction; /** * Abstract base class for all devices connected through Bluetooth Low Energy (LE) aka diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java index b7a7e9389..258199ad8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java @@ -113,6 +113,10 @@ public abstract class AbstractBTLEOperation return operationStatus == OperationStatus.RUNNING; } + public boolean isOperationFinished() { + return operationStatus == OperationStatus.FINISHED; + } + public T getSupport() { return mSupport; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java index b74cdbfdb..4839a9924 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java @@ -157,11 +157,12 @@ public final class BtLEQueue { LOG.info("Attempting to connect to " + mGbDevice.getName()); mBluetoothAdapter.cancelDiscovery(); BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress()); + boolean result; synchronized (mGattMonitor) { - mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback); -// result = mBluetoothGatt.connect(); + mBluetoothGatt = remoteDevice.connectGatt(mContext, true, internalGattCallback); + result = mBluetoothGatt.connect(); } - boolean result = mBluetoothGatt != null; +// boolean result = mBluetoothGatt != null; if (result) { setDeviceConnectionState(State.CONNECTING); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/CheckInitializedAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/CheckInitializedAction.java similarity index 85% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/CheckInitializedAction.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/CheckInitializedAction.java index 21bc0be87..414d983fc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/CheckInitializedAction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/CheckInitializedAction.java @@ -1,10 +1,9 @@ -package nodomain.freeyourgadget.gadgetbridge.service.btle; +package nodomain.freeyourgadget.gadgetbridge.service.btle.actions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction; /** * A special action that is executed at the very front of the initialization diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ConditionalWriteAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ConditionalWriteAction.java new file mode 100644 index 000000000..6d373fafb --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ConditionalWriteAction.java @@ -0,0 +1,30 @@ +package nodomain.freeyourgadget.gadgetbridge.service.btle.actions; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; + +public abstract class ConditionalWriteAction extends WriteAction { + public ConditionalWriteAction(BluetoothGattCharacteristic characteristic) { + super(characteristic, null); + } + + @Override + protected boolean writeValue(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value) { + byte[] conditionalValue = checkCondition(); + if (conditionalValue != null) { + return super.writeValue(gatt, characteristic, conditionalValue); + } + return true; + } + + /** + * Checks the condition whether the write shall happen or not. + * Returns the actual value to be written or null in case nothing shall be written. + *

+ * Note that returning null will not cause run() to return false, in other words, + * the rest of the queue will still be executed. + * + * @return the value to be written or null to not write anything + */ + protected abstract byte[] checkCondition(); +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java index b4ec95489..5cecee06d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java @@ -22,16 +22,26 @@ public class WriteAction extends BtLEAction { @Override public boolean run(BluetoothGatt gatt) { - int properties = getCharacteristic().getProperties(); + BluetoothGattCharacteristic characteristic = getCharacteristic(); + int properties = characteristic.getProperties(); //TODO: expectsResult should return false if PROPERTY_WRITE_NO_RESPONSE is true, but this yelds to timing issues if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0 || ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0)) { - if (getCharacteristic().setValue(value)) { - return gatt.writeCharacteristic(getCharacteristic()); - } + return writeValue(gatt, characteristic, value); } return false; } + protected boolean writeValue(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value) { + if (characteristic.setValue(value)) { + return gatt.writeCharacteristic(characteristic); + } + return false; + } + + protected final byte[] getValue() { + return value; + } + @Override public boolean expectsResult() { return true; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java index 6f25b1634..8415c6958 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java @@ -1,11 +1,9 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; -import android.bluetooth.BluetoothGatt; - import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.PlainAction; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction; -public class CheckAuthenticationNeededAction extends PlainAction { +public class CheckAuthenticationNeededAction extends AbortTransactionAction { private final GBDevice mDevice; public CheckAuthenticationNeededAction(GBDevice device) { @@ -14,14 +12,14 @@ public class CheckAuthenticationNeededAction extends PlainAction { } @Override - public boolean run(BluetoothGatt gatt) { + protected boolean shouldAbort() { // the state is set in MiBandSupport.handleNotificationNotif() switch (mDevice.getState()) { case AUTHENTICATION_REQUIRED: // fall through case AUTHENTICATING: - return false; // abort the whole thing + return true; // abort the whole thing default: - return true; + return false; } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java index 217988781..1072ff50c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java @@ -109,7 +109,7 @@ public class DeviceInfo extends AbstractInfo { public boolean isMili1S() { // TODO: this is probably not quite correct, but hopefully sufficient for early 1S support - return feature == 4 && appearance == 0 || feature == 4 && hwVersion == 4; + return (feature == 4 && appearance == 0) || hwVersion == 4; } public String getHwVersion() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java index 733b89acb..37b225919 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java @@ -34,16 +34,18 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; -import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ConditionalWriteAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.FetchActivityOperation; @@ -109,6 +111,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { .sendUserInfo(builder) .checkAuthenticationNeeded(builder, getDevice()) .setWearLocation(builder) + .setHeartrateSleepSupport(builder) .setFitnessGoal(builder) .enableFurtherNotifications(builder, true) .setCurrentTime(builder) @@ -368,6 +371,44 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { return this; } + @Override + public void onEnableHeartRateSleepSupport(boolean enable) { + try { + TransactionBuilder builder = performInitialized("enable heart rate sleep support: " + enable); + setHeartrateSleepSupport(builder); + builder.queue(getQueue()); + } catch (IOException e) { + GB.toast(getContext(), "Error toggling heart rate sleep support: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + } + + /** + * Part of device initialization process. Do not call manually. + * + * @param builder + */ + private MiBandSupport setHeartrateSleepSupport(TransactionBuilder builder) { + BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT); + if (characteristic != null) { + builder.add(new ConditionalWriteAction(characteristic) { + @Override + protected byte[] checkCondition() { + if (!supportsHeartRate()) { + return null; + } + if (MiBandCoordinator.getHeartrateSleepSupport(getDevice().getAddress())) { + LOG.info("Enabling heartrate sleep support..."); + return startHeartMeasurementSleep; + } else { + LOG.info("Disabling heartrate sleep support..."); + return stopHeartMeasurementSleep; + } + } + }); + } + return this; + } + private void performDefaultNotification(String task, short repeat, BtLEAction extraAction) { try { TransactionBuilder builder = performInitialized(task); @@ -519,8 +560,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } @Override - public void onSetCallState(String number, String name, ServiceCommand command) { - if (ServiceCommand.CALL_INCOMING.equals(command)) { + public void onSetCallState(CallSpec callSpec) { + if (callSpec.command == CallSpec.CALL_INCOMING) { telephoneRinging = true; AbortTransactionAction abortAction = new AbortTransactionAction() { @Override @@ -529,7 +570,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } }; performPreferredNotification("incoming call", MiBandConst.ORIGIN_INCOMING_CALL, abortAction); - } else if (ServiceCommand.CALL_START.equals(command) || ServiceCommand.CALL_END.equals(command)) { + } else if ((callSpec.command == CallSpec.CALL_START) || (callSpec.command == CallSpec.CALL_END)) { telephoneRinging = false; } } @@ -540,7 +581,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } @Override - public void onSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) { + public void onSetMusicInfo(MusicSpec musicSpec) { // not supported } @@ -725,6 +766,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { handleBatteryInfo(characteristic.getValue(), status); } else if (MiBandService.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) { logHeartrate(characteristic.getValue()); + } else if (MiBandService.UUID_CHARACTERISTIC_DATE_TIME.equals(characteristicUUID)) { + logDate(characteristic.getValue()); } else { LOG.info("Unhandled characteristic read: " + characteristicUUID); logMessageContent(characteristic.getValue()); @@ -756,6 +799,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } } + public void logDate(byte[] value) { + GregorianCalendar calendar = MiBandDateConverter.rawBytesToCalendar(value); + LOG.info("Got Mi Band Date: " + DateTimeUtils.formatDateTime(calendar.getTime())); + } + public void logHeartrate(byte[] value) { LOG.info("Got heartrate:"); if (value.length == 2 && value[0] == 6) { @@ -889,7 +937,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { if (status != BluetoothGatt.GATT_SUCCESS) { LOG.warn("Could not write to the control point."); } - LOG.info("handleControlPoint write status:" + status); + LOG.info("handleControlPoint write status:" + status + "; length: " + (value != null ? value.length : "(null)")); if (value != null) { for (byte b : value) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java index 3acff6e6d..4bb7d6ba3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java @@ -2,6 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; +import android.content.Context; import android.net.Uri; import android.widget.Toast; @@ -13,6 +14,7 @@ import java.util.Arrays; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandFWHelper; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; @@ -53,13 +55,14 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { updateCoordinator.initNextOperation(); if (!updateCoordinator.sendFwInfo()) { - GB.toast(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR); + displayMessage(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR); done(); } //the firmware will be sent by the notification listener if the band confirms that the metadata are ok. } private void done() { + LOG.info("Operation done."); updateCoordinator = null; operationFinished(); unsetBusy(); @@ -93,36 +96,39 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { return; } if (updateCoordinator == null) { - LOG.error("received notification when updateCoordinator is null, ignoring!"); + LOG.error("received notification when updateCoordinator is null, ignoring (notification content follows):"); + getSupport().logMessageContent(value); return; } switch (value[0]) { case MiBandService.NOTIFY_FW_CHECK_SUCCESS: if (firmwareInfoSent) { - GB.toast(getContext(), "Firmware metadata successfully sent.", Toast.LENGTH_LONG, GB.INFO); + displayMessage(getContext(), "Firmware metadata successfully sent.", Toast.LENGTH_LONG, GB.INFO); if (!updateCoordinator.sendFwData()) { - GB.toast(getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR); + displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR); done(); } firmwareInfoSent = false; + } else { + LOG.warn("firmwareInfoSent is false -- not sending firmware data even though we got meta data success notification"); } break; case MiBandService.NOTIFY_FW_CHECK_FAILED: - GB.toast(getContext().getString(R.string.updatefirmwareoperation_metadata_updateproblem), Toast.LENGTH_LONG, GB.ERROR); + displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_metadata_updateproblem), Toast.LENGTH_LONG, GB.ERROR); firmwareInfoSent = false; done(); break; case MiBandService.NOTIFY_FIRMWARE_UPDATE_SUCCESS: if (updateCoordinator.initNextOperation()) { - GB.toast(getContext(), "Heart Rate Firmware successfully updated, now updating Mi Band Firmware", Toast.LENGTH_LONG, GB.INFO); + displayMessage(getContext(), "Heart Rate Firmware successfully updated, now updating Mi Band Firmware", Toast.LENGTH_LONG, GB.INFO); if (!updateCoordinator.sendFwInfo()) { - GB.toast(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR); + displayMessage(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR); done(); } break; } else if (updateCoordinator.needsReboot()) { - GB.toast(getContext(), getContext().getString(R.string.updatefirmwareoperation_update_complete_rebooting), Toast.LENGTH_LONG, GB.INFO); + displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_update_complete_rebooting), Toast.LENGTH_LONG, GB.INFO); GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext()); getSupport().onReboot(); } else { @@ -132,7 +138,7 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { break; case MiBandService.NOTIFY_FIRMWARE_UPDATE_FAILED: //TODO: the firmware transfer failed, but the miband should be still functional with the old firmware. What should we do? - GB.toast(getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR); + displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR); GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_write_failed), false, 0, getContext()); done(); break; @@ -143,6 +149,10 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { } } + private void displayMessage(Context context, String message, int duration, int severity) { + getSupport().handleGBDeviceEvent(new GBDeviceEventDisplayMessage(message, duration, severity)); + } + /** * Prepare the MiBand to receive the new firmware data. * Some information about the new firmware version have to be pushed to the MiBand before sending @@ -193,7 +203,7 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { } else { LOG.info("is multi Mi Band firmware, sending fw2 (hr) first"); byte[] fw2Info = prepareFirmwareInfo(fw2Bytes, fw2OldVersion, fw2Version, fw2Checksum, 1, rebootWhenFinished /*, progress monitor */); - byte[] fw1Info = prepareFirmwareInfo(fw1Bytes, fw1OldVersion, fw1Version, fw1Checksum, 1, rebootWhenFinished /*, progress monitor */); + byte[] fw1Info = prepareFirmwareInfo(fw1Bytes, fw1OldVersion, fw1Version, fw1Checksum, 0, rebootWhenFinished /*, progress monitor */); return new DoubleUpdateCoordinator(fw1Info, fw1Bytes, fw2Info, fw2Bytes, rebootWhenFinished); } } @@ -268,31 +278,34 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { final int packetLength = 20; int packets = len / packetLength; - // going from 0 to len - int firmwareProgress = 0; - + BluetoothGattCharacteristic characteristicControlPoint = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT); + BluetoothGattCharacteristic characteristicFWData = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_FIRMWARE_DATA); try { + // going from 0 to len + int firmwareProgress = 0; + TransactionBuilder builder = performInitialized("send firmware packet"); +// getSupport().setLowLatency(builder); for (int i = 0; i < packets; i++) { byte[] fwChunk = Arrays.copyOfRange(fwbytes, i * packetLength, i * packetLength + packetLength); - builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_FIRMWARE_DATA), fwChunk); + builder.write(characteristicFWData, fwChunk); firmwareProgress += packetLength; - int progressPercent = (int) (((float) firmwareProgress) / len) * 100; + int progressPercent = (int) ((((float) firmwareProgress) / len) * 100); if ((i > 0) && (i % 50 == 0)) { - builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), new byte[]{MiBandService.COMMAND_SYNC}); + builder.write(characteristicControlPoint, new byte[]{MiBandService.COMMAND_SYNC}); builder.add(new SetProgressAction(getContext().getString(R.string.updatefirmwareoperation_update_in_progress), true, progressPercent, getContext())); } } if (firmwareProgress < len) { byte[] lastChunk = Arrays.copyOfRange(fwbytes, packets * packetLength, len); - builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_FIRMWARE_DATA), lastChunk); + builder.write(characteristicFWData, lastChunk); firmwareProgress = len; } - builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), new byte[]{MiBandService.COMMAND_SYNC}); + builder.write(characteristicControlPoint, new byte[]{MiBandService.COMMAND_SYNC}); builder.queue(getQueue()); } catch (IOException ex) { @@ -319,9 +332,10 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { public boolean sendFwInfo() { try { TransactionBuilder builder = performInitialized("send firmware info"); +// getSupport().setLowLatency(builder); builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.updating_firmware), getContext())); + builder.add(new FirmwareInfoSentAction()); // Note: *before* actually sending the info, otherwise it's too late! builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), getFirmwareInfo()); - builder.add(new FirmwareInfoSucceededAction()); builder.queue(getQueue()); return true; } catch (IOException e) { @@ -437,10 +451,12 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { } } - private class FirmwareInfoSucceededAction extends PlainAction { + private class FirmwareInfoSentAction extends PlainAction { @Override public boolean run(BluetoothGatt gatt) { - firmwareInfoSent = true; + if (isOperationRunning()) { + firmwareInfoSent = true; + } return true; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java index 8528287fd..7d06876fd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java @@ -21,22 +21,33 @@ class DatalogSessionHealthSleep extends DatalogSession { public DatalogSessionHealthSleep(byte id, UUID uuid, int tag, byte item_type, short item_size) { super(id, uuid, tag, item_type, item_size); - taginfo = "(health - sleep)"; + taginfo = "(health - sleep " + tag + " )"; } @Override public boolean handleMessage(ByteBuffer datalogMessage, int length) { LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length)); + switch (this.tag) { + case 83: + return handleMessage83(datalogMessage, length); + case 84: + return handleMessage84(datalogMessage, length); + default: + return false; + } + } + private boolean handleMessage84(ByteBuffer datalogMessage, int length) { int initialPosition = datalogMessage.position(); int beginOfRecordPosition; short recordVersion; //probably + short recordType; //probably: 1=sleep, 2=deep sleep if (0 != (length % itemSize)) return false;//malformed message? int recordCount = length / itemSize; - SleepRecord[] sleepRecords = new SleepRecord[recordCount]; + SleepRecord84[] sleepRecords = new SleepRecord84[recordCount]; for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) { beginOfRecordPosition = initialPosition + recordIdx * itemSize; @@ -45,26 +56,30 @@ class DatalogSessionHealthSleep extends DatalogSession { if (recordVersion != 1) return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it - sleepRecords[recordIdx] = new SleepRecord(datalogMessage.getInt(), - datalogMessage.getInt(), - datalogMessage.getInt(), - datalogMessage.getInt()); + datalogMessage.getShort();//throwaway, unknown + recordType = datalogMessage.getShort(); + + sleepRecords[recordIdx] = new SleepRecord84(recordType, datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt()); } - return store(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again. + return store84(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again. } - private boolean store(SleepRecord[] sleepRecords) { + private boolean store84(SleepRecord84[] sleepRecords) { DBHandler dbHandler = null; SampleProvider sampleProvider = new HealthSampleProvider(); - GB.toast("We don't know how to store deep sleep from the pebble yet.", Toast.LENGTH_LONG, GB.INFO); try { dbHandler = GBApplication.acquireDB(); int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider); - for (SleepRecord sleepRecord : sleepRecords) { - if (latestTimestamp < sleepRecord.bedTimeEnd) + for (SleepRecord84 sleepRecord : sleepRecords) { + if (latestTimestamp < (sleepRecord.timestampStart + sleepRecord.durationSeconds)) return false; - dbHandler.changeStoredSamplesType(sleepRecord.bedTimeStart, sleepRecord.bedTimeEnd, sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider); + if (sleepRecord.type == 2) { + dbHandler.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP), sampleProvider); + } else { + dbHandler.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider); + } + } } catch (Exception ex) { LOG.debug(ex.getMessage()); @@ -76,17 +91,80 @@ class DatalogSessionHealthSleep extends DatalogSession { return true; } - private class SleepRecord { + private boolean handleMessage83(ByteBuffer datalogMessage, int length) { + int initialPosition = datalogMessage.position(); + int beginOfRecordPosition; + short recordVersion; //probably + + if (0 != (length % itemSize)) + return false;//malformed message? + + int recordCount = length / itemSize; + SleepRecord83[] sleepRecords = new SleepRecord83[recordCount]; + + for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) { + beginOfRecordPosition = initialPosition + recordIdx * itemSize; + datalogMessage.position(beginOfRecordPosition);//we may not consume all the bytes of a record + recordVersion = datalogMessage.getShort(); + if (recordVersion != 1) + return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it + + sleepRecords[recordIdx] = new SleepRecord83(datalogMessage.getInt(), + datalogMessage.getInt(), + datalogMessage.getInt(), + datalogMessage.getInt()); + } + + return store83(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again. + } + + private boolean store83(SleepRecord83[] sleepRecords) { + DBHandler dbHandler = null; + SampleProvider sampleProvider = new HealthSampleProvider(); + GB.toast("Deep sleep is supported only from firmware 3.11 onwards.", Toast.LENGTH_LONG, GB.INFO); + try { + dbHandler = GBApplication.acquireDB(); + int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider); + for (SleepRecord83 sleepRecord : sleepRecords) { + if (latestTimestamp < sleepRecord.bedTimeEnd) + return false; + dbHandler.changeStoredSamplesType(sleepRecord.bedTimeStart, sleepRecord.bedTimeEnd, sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider); + } + } catch (Exception ex) { + LOG.debug(ex.getMessage()); + } finally { + if (dbHandler != null) { + dbHandler.release(); + } + } + return true; + } + + private class SleepRecord83 { int offsetUTC; //probably int bedTimeStart; int bedTimeEnd; int deepSleepSeconds; - public SleepRecord(int offsetUTC, int bedTimeStart, int bedTimeEnd, int deepSleepSeconds) { + public SleepRecord83(int offsetUTC, int bedTimeStart, int bedTimeEnd, int deepSleepSeconds) { this.offsetUTC = offsetUTC; this.bedTimeStart = bedTimeStart; this.bedTimeEnd = bedTimeEnd; this.deepSleepSeconds = deepSleepSeconds; } } + + private class SleepRecord84 { + int type; //1=sleep, 2=deep sleep + int offsetUTC; //probably + int timestampStart; + int durationSeconds; + + public SleepRecord84(int type, int offsetUTC, int timestampStart, int durationSeconds) { + this.type = type; + this.offsetUTC = offsetUTC; + this.timestampStart = timestampStart; + this.durationSeconds = durationSeconds; + } + } } \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java index 81bb43192..f89f03639 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java @@ -46,7 +46,7 @@ public class DatalogSessionHealthSteps extends DatalogSession { recordVersion = datalogMessage.getShort(); - if (recordVersion != 5) + if ((recordVersion != 5) && (recordVersion != 6)) return false; //we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it timestamp = datalogMessage.getInt(); @@ -59,8 +59,7 @@ public class DatalogSessionHealthSteps extends DatalogSession { for (int recordIdx = 0; recordIdx < recordNum; recordIdx++) { datalogMessage.position(beginOfRecordPosition + recordIdx * recordLength); //we may not consume all the bytes of a record - stepsRecords[recordIdx] = new StepsRecord(timestamp, datalogMessage.get() & 0xff, datalogMessage.get() & 0xff, datalogMessage.getShort() & 0xffff); - datalogMessage.getShort(); // skip + stepsRecords[recordIdx] = new StepsRecord(timestamp, datalogMessage.get() & 0xff, datalogMessage.get() & 0xff, datalogMessage.getShort() & 0xffff, datalogMessage.get() & 0xff); timestamp += 60; } @@ -102,12 +101,14 @@ public class DatalogSessionHealthSteps extends DatalogSession { int steps; int orientation; int intensity; + int light_intensity; - public StepsRecord(int timestamp, int steps, int orientation, int intensity) { + public StepsRecord(int timestamp, int steps, int orientation, int intensity, int light_intensity) { this.timestamp = timestamp; this.steps = steps; this.orientation = orientation; this.intensity = intensity; + this.light_intensity = light_intensity; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java index b5bfe222d..b2a42630a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java @@ -200,11 +200,16 @@ public class PebbleIoThread extends GBDeviceIoThread { } mPebbleProtocol.setForceProtocol(sharedPrefs.getBoolean("pebble_force_protocol", false)); - gbDevice.setState(GBDevice.State.CONNECTED); - gbDevice.sendDeviceUpdateIntent(getContext()); mIsConnected = true; - write(mPebbleProtocol.encodeFirmwareVersionReq()); + if (originalState == GBDevice.State.WAITING_FOR_RECONNECT) { + gbDevice.setState(GBDevice.State.INITIALIZED); + } else { + gbDevice.setState(GBDevice.State.CONNECTED); + write(mPebbleProtocol.encodeFirmwareVersionReq()); + } + gbDevice.sendDeviceUpdateIntent(getContext()); + return true; } @@ -363,9 +368,19 @@ public class PebbleIoThread extends GBDeviceIoThread { if (reconnectAttempts > 0) { gbDevice.setState(GBDevice.State.CONNECTING); gbDevice.sendDeviceUpdateIntent(getContext()); + int delaySeconds = 1; while (reconnectAttempts-- > 0 && !mQuit && !mIsConnected) { LOG.info("Trying to reconnect (attempts left " + reconnectAttempts + ")"); mIsConnected = connect(gbDevice.getAddress()); + if (!mIsConnected) { + try { + Thread.sleep(delaySeconds * 1000); + } catch (InterruptedException ignored) { + } + if (delaySeconds < 64) { + delaySeconds *= 2; + } + } } } if (!mIsConnected && !mQuit) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java index fc00f3274..5d261e6a0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java @@ -33,9 +33,9 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; -import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; public class PebbleProtocol extends GBDeviceProtocol { @@ -495,7 +495,7 @@ public class PebbleProtocol extends GBDeviceProtocol { @Override public byte[] encodeFindDevice(boolean start) { - return encodeSetCallState("Where are you?", "Gadgetbridge", start ? ServiceCommand.CALL_INCOMING : ServiceCommand.CALL_END); + return encodeSetCallState("Where are you?", "Gadgetbridge", start ? CallSpec.CALL_INCOMING : CallSpec.CALL_END); } private static byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, String[] cannedReplies) { @@ -1044,20 +1044,20 @@ public class PebbleProtocol extends GBDeviceProtocol { } @Override - public byte[] encodeSetCallState(String number, String name, ServiceCommand command) { + public byte[] encodeSetCallState(String number, String name, int command) { String[] parts = {number, name}; byte pebbleCmd; switch (command) { - case CALL_START: + case CallSpec.CALL_START: pebbleCmd = PHONECONTROL_START; break; - case CALL_END: + case CallSpec.CALL_END: pebbleCmd = PHONECONTROL_END; break; - case CALL_INCOMING: + case CallSpec.CALL_INCOMING: pebbleCmd = PHONECONTROL_INCOMINGCALL; break; - case CALL_OUTGOING: + case CallSpec.CALL_OUTGOING: // pebbleCmd = PHONECONTROL_OUTGOINGCALL; /* * HACK/WORKAROUND for non-working outgoing call display. @@ -1876,12 +1876,12 @@ public class PebbleProtocol extends GBDeviceProtocol { int timestamp = buf.getInt(); int log_tag = buf.getInt(); byte item_type = buf.get(); - short item_size = buf.get(); + short item_size = buf.getShort(); LOG.info("DATALOG OPENSESSION. id=" + (id & 0xff) + ", App UUID=" + uuid.toString() + ", log_tag=" + log_tag + ", item_type=" + item_type + ", itemSize=" + item_size); if (!mDatalogSessions.containsKey(id)) { if (uuid.equals(UUID_ZERO) && log_tag == 81) { mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, log_tag, item_type, item_size)); - } else if (uuid.equals(UUID_ZERO) && log_tag == 83) { + } else if (uuid.equals(UUID_ZERO) && (log_tag == 83 || log_tag == 84)) { mDatalogSessions.put(id, new DatalogSessionHealthSleep(id, uuid, log_tag, item_type, item_size)); } else { mDatalogSessions.put(id, new DatalogSession(id, uuid, log_tag, item_type, item_size)); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java index 66560c0d0..74e4736d9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java @@ -10,7 +10,11 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.UUID; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; @@ -35,7 +39,7 @@ public class PebbleSupport extends AbstractSerialDeviceSupport { @Override public boolean useAutoConnect() { - return false; + return true; } @Override @@ -71,6 +75,45 @@ public class PebbleSupport extends AbstractSerialDeviceSupport { return (PebbleIoThread) super.getDeviceIOThread(); } + private boolean reconnect() { + if (!isConnected() && useAutoConnect()) { + if (getDevice().getState() == GBDevice.State.WAITING_FOR_RECONNECT) { + gbDeviceIOThread.interrupt(); + gbDeviceIOThread = null; + if (!connect()) { + return false; + } + try { + Thread.sleep(4000); // this is about the time the connect takes, so the notification can come though + } catch (InterruptedException ignored) { + } + } + } + return true; + } + + @Override + public void onNotification(NotificationSpec notificationSpec) { + if (reconnect()) { + super.onNotification(notificationSpec); + } + } + + @Override + public void onSetCallState(CallSpec callSpec) { + if (reconnect()) { + super.onSetCallState(callSpec); + } + } + + @Override + public void onSetMusicInfo(MusicSpec musicSpec) { + if (reconnect()) { + super.onSetMusicInfo(musicSpec); + } + } + + @Override public void onSetAlarms(ArrayList alarms) { //nothing to do ATM diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java index 5563de599..aa78a3936 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java @@ -8,8 +8,9 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; -import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport; /** @@ -29,8 +30,8 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport private static final Logger LOG = LoggerFactory.getLogger(AbstractDeviceSupport.class); - private GBDeviceProtocol gbDeviceProtocol; - private GBDeviceIoThread gbDeviceIOThread; + protected GBDeviceProtocol gbDeviceProtocol; + protected GBDeviceIoThread gbDeviceIOThread; /** * Factory method to create the device specific GBDeviceProtocol instance to be used. @@ -47,11 +48,7 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport // currently only one thread allowed if (gbDeviceIOThread != null) { gbDeviceIOThread.quit(); - try { - gbDeviceIOThread.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } + gbDeviceIOThread.interrupt(); gbDeviceIOThread = null; } } @@ -120,14 +117,14 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport } @Override - public void onSetCallState(String number, String name, ServiceCommand command) { - byte[] bytes = gbDeviceProtocol.encodeSetCallState(number, name, command); + public void onSetCallState(CallSpec callSpec) { + byte[] bytes = gbDeviceProtocol.encodeSetCallState(callSpec.number, callSpec.name, callSpec.command); sendToDevice(bytes); } @Override - public void onSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) { - byte[] bytes = gbDeviceProtocol.encodeSetMusicInfo(artist, album, track, duration, trackCount, trackNr); + public void onSetMusicInfo(MusicSpec musicSpec) { + byte[] bytes = gbDeviceProtocol.encodeSetMusicInfo(musicSpec.artist, musicSpec.album, musicSpec.track, musicSpec.duration, musicSpec.trackCount, musicSpec.trackNr); sendToDevice(bytes); } @@ -178,4 +175,10 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport byte[] bytes = gbDeviceProtocol.encodeEnableRealtimeSteps(enable); sendToDevice(bytes); } + + @Override + public void onEnableHeartRateSleepSupport(boolean enable) { + byte[] bytes = gbDeviceProtocol.encodeEnableHeartRateSleepSupport(enable); + sendToDevice(bytes); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java index b08b105d5..2aa1fb660 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java @@ -4,7 +4,6 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; -import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; public abstract class GBDeviceProtocol { @@ -16,7 +15,7 @@ public abstract class GBDeviceProtocol { return null; } - public byte[] encodeSetCallState(String number, String name, ServiceCommand command) { + public byte[] encodeSetCallState(String number, String name, int command) { return null; } @@ -60,6 +59,10 @@ public abstract class GBDeviceProtocol { return null; } + public byte[] encodeEnableHeartRateSleepSupport(boolean enable) { + return null; + } + public GBDeviceEvent[] decodeResponse(byte[] responseData) { return null; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java index c6f87cff6..a20e0f2eb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java @@ -39,6 +39,10 @@ public class GB { public static final int INFO = 1; public static final int WARN = 2; public static final int ERROR = 3; + public static final String ACTION_DISPLAY_MESSAGE = "GB_Display_Message"; + public static final String DISPLAY_MESSAGE_MESSAGE = "message"; + public static final String DISPLAY_MESSAGE_DURATION = "duration"; + public static final String DISPLAY_MESSAGE_SEVERITY = "severity"; public static GBEnvironment environment; public static Notification createNotification(String text, Context context) { @@ -225,7 +229,7 @@ public class GB { } } - private static void log(String message, int severity, Throwable ex) { + public static void log(String message, int severity, Throwable ex) { switch (severity) { case INFO: LOG.info(message, ex); diff --git a/app/src/main/res/layout/activity_appinstaller.xml b/app/src/main/res/layout/activity_appinstaller.xml index 8846623b0..eb13abc1b 100644 --- a/app/src/main/res/layout/activity_appinstaller.xml +++ b/app/src/main/res/layout/activity_appinstaller.xml @@ -62,6 +62,14 @@ android:layout_below="@+id/installProgressBar" android:layout_marginTop="10dp" /> + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 1ca2b0ed2..6cf3ba015 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -102,7 +102,7 @@ 有効なユーザーデータはありません。今のところ、ダミーのユーザーデータを使用します。 お使いのMi Bandが振動と点滅したとき、それを連続して数回タップしてください。 インストール - お使いのデバイスを検出可能にしてください。現在、接続されたデバイスは、おそらく検出されません。 + お使いのデバイスを検出可能にしてください。現在、接続されたデバイスは、おそらく検出されません。お使いのデバイスが 2 分しても表示されない場合は、モバイルデバイスを再起動した後にもう一度試してください。 注: デバイスイメージ 名前/別名 @@ -197,6 +197,7 @@ 非互換性のファームウェア このファームウェアは、デバイスと互換性がありません 今後のイベントのために予約するアラーム + 睡眠の検出を改善するために心拍センサーを使用する 再接続の待機中 再インストール あなたについて @@ -217,4 +218,6 @@ FW: %1$s ログファイルのディレクトリを作成中にエラー: %1$s HR: + ファームウェアを更新しています + ファームウェアを送信しませんでした diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f5a0bc4f9..8d5f07fd3 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -11,7 +11,7 @@ #ff808080 #1f000000 - #b40000 + #ffab40 #0071b7 #4c5aff diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3bcbddd84..407d428f3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -213,6 +213,8 @@ Incompatible firmware This firmware is not compatible with the device Alarms to reserve for upcoming events + Use Heartrate Sensor to improve sleep detection + waiting for reconnect Reinstall @@ -237,5 +239,6 @@ "HR: " Firmware update in progress Firmware not sent + Heart Rate diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml index 608a543c8..dddd9bab6 100644 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -1,5 +1,16 @@ + + Pebble: support pebble health datalog messages of firmware 3.11 (this adds support for deep sleep!) + Pebble: try to reconnect on new notifications and phone calls when connection was lost unexpectedly + Pebble: delay between reconnection attempts (from 1 up to 64 seconds) + + + Pebble: Fix Pebble Health activation (was not available in the App Manager) + Simplify connection state display (only connecting->connected) + Small improvements to the pairing activity + Mi Band 1S: Fix for mi band firmware update + Mi Band: Fix update of second (HR) firmware on Mi1S (#234) Fix ordering issue of device infos being displayed diff --git a/app/src/main/res/xml/miband_preferences.xml b/app/src/main/res/xml/miband_preferences.xml index 65997e955..042cd8e1e 100644 --- a/app/src/main/res/xml/miband_preferences.xml +++ b/app/src/main/res/xml/miband_preferences.xml @@ -30,6 +30,10 @@ android:maxLength="1" android:digits="0123" android:title="@string/miband_prefs_reserve_alarm_calendar" /> +