From 93e05e8dd6059f0803093b692e98666e4b100d35 Mon Sep 17 00:00:00 2001 From: Daniel Dakhno Date: Sat, 28 May 2022 02:10:45 +0200 Subject: [PATCH 01/31] Fossil HR: fixed null music spec --- .../qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java index bdd0a63f64..711aa3251b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java @@ -943,6 +943,10 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { @Override public void setMusicInfo(MusicSpec musicSpec) { + musicSpec = new MusicSpec(musicSpec); + if(musicSpec.album == null) musicSpec.album = ""; + if(musicSpec.artist == null) musicSpec.artist = ""; + if(musicSpec.track == null) musicSpec.track = ""; if ( currentSpec != null && currentSpec.album.equals(musicSpec.album) From 4db2877a91f037c7ee6dde73d12267934f0e3074 Mon Sep 17 00:00:00 2001 From: vanous Date: Sat, 28 May 2022 14:57:01 +0200 Subject: [PATCH 02/31] Widget and SleepAlarmWidget: modify for multidevice support --- app/src/main/AndroidManifest.xml | 6 + .../gadgetbridge/SleepAlarmWidget.java | 35 +++-- .../freeyourgadget/gadgetbridge/Widget.java | 64 +++----- ...SleepAlarmWidgetConfigurationActivity.java | 139 ++++++++++++++++++ .../activities/WidgetAlarmsActivity.java | 23 +-- .../WidgetConfigurationActivity.java | 3 +- .../util/WidgetPreferenceStorage.java | 31 ++++ .../main/res/xml/sleep_alarm_widget_info.xml | 4 +- 8 files changed, 229 insertions(+), 76 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepAlarmWidgetConfigurationActivity.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6a424dfb72..b1a42c75b6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -590,6 +590,12 @@ + + + + + + devices = gbApp.getDeviceManager().getDevices(); - for (GBDevice device : devices) { - if (device.getAddress().equals(HwAddress)) { - return device; - } - } - return null; - } - private long[] getSteps() { + private long[] getSteps(GBDevice gbDevice) { Context context = GBApplication.getContext(); Calendar day = GregorianCalendar.getInstance(); @@ -108,7 +85,7 @@ public class Widget extends AppWidgetProvider { return new long[]{0, 0, 0}; } DailyTotals ds = new DailyTotals(); - return ds.getDailyTotalsForDevice(selectedDevice, day); + return ds.getDailyTotalsForDevice(gbDevice, day); //return ds.getDailyTotalsForAllDevices(day); } @@ -119,11 +96,10 @@ public class Widget extends AppWidgetProvider { private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { - selectedDevice = getSelectedDevice(); - WidgetPreferenceStorage widgetPreferenceStorage = new WidgetPreferenceStorage(); - String savedDeviceAddress = widgetPreferenceStorage.getSavedDeviceAddress(context, appWidgetId); - if (savedDeviceAddress != null) { - selectedDevice = getDeviceByMAC(context.getApplicationContext(), savedDeviceAddress); //this would probably only happen if device no longer exists in GB + GBDevice deviceForWidget = new WidgetPreferenceStorage().getDeviceForWidget(appWidgetId); + if (deviceForWidget == null) { + LOG.debug("Widget: no device, bailing out"); + return; } RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget); @@ -143,17 +119,17 @@ public class Widget extends AppWidgetProvider { //alarms popup menu Intent startAlarmListIntent = new Intent(context, WidgetAlarmsActivity.class); - startAlarmListIntent.putExtra(GBDevice.EXTRA_DEVICE, selectedDevice); + startAlarmListIntent.putExtra(GBDevice.EXTRA_DEVICE, deviceForWidget); PendingIntent startAlarmListPIntent = PendingIntent.getActivity(context, appWidgetId, startAlarmListIntent, PendingIntent.FLAG_UPDATE_CURRENT); views.setOnClickPendingIntent(R.id.todaywidget_header_alarm_icon, startAlarmListPIntent); //charts Intent startChartsIntent = new Intent(context, ChartsActivity.class); - startChartsIntent.putExtra(GBDevice.EXTRA_DEVICE, selectedDevice); + startChartsIntent.putExtra(GBDevice.EXTRA_DEVICE, deviceForWidget); PendingIntent startChartsPIntent = PendingIntent.getActivity(context, appWidgetId, startChartsIntent, PendingIntent.FLAG_CANCEL_CURRENT); views.setOnClickPendingIntent(R.id.todaywidget_bottom_layout, startChartsPIntent); - long[] dailyTotals = getSteps(); + long[] dailyTotals = getSteps(deviceForWidget); int steps = (int) dailyTotals[0]; int sleep = (int) dailyTotals[1]; ActivityUser activityUser = new ActivityUser(); @@ -172,17 +148,17 @@ public class Widget extends AppWidgetProvider { views.setProgressBar(R.id.todaywidget_sleep_progress, sleepGoalMinutes, sleep, false); views.setProgressBar(R.id.todaywidget_distance_progress, distanceGoal, steps * stepLength, false); views.setViewVisibility(R.id.todaywidget_battery_icon, View.GONE); - if (selectedDevice != null) { - String status = String.format("%1s", selectedDevice.getStateString()); - if (selectedDevice.isConnected()) { - if (selectedDevice.getBatteryLevel() > 1) { + if (deviceForWidget != null) { + String status = String.format("%1s", deviceForWidget.getStateString()); + if (deviceForWidget.isConnected()) { + if (deviceForWidget.getBatteryLevel() > 1) { views.setViewVisibility(R.id.todaywidget_battery_icon, View.VISIBLE); - status = String.format("%1s%%", selectedDevice.getBatteryLevel()); + status = String.format("%1s%%", deviceForWidget.getBatteryLevel()); } } - String deviceName = selectedDevice.getAlias() != null ? selectedDevice.getAlias() : selectedDevice.getName(); + String deviceName = deviceForWidget.getAlias() != null ? deviceForWidget.getAlias() : deviceForWidget.getName(); views.setTextViewText(R.id.todaywidget_device_status, status); views.setTextViewText(R.id.todaywidget_device_name, deviceName); } @@ -191,11 +167,11 @@ public class Widget extends AppWidgetProvider { appWidgetManager.updateAppWidget(appWidgetId, views); } - public void refreshData() { + public void refreshData(int appWidgetId) { Context context = GBApplication.getContext(); - GBDevice device = getSelectedDevice(); + GBDevice deviceForWidget = new WidgetPreferenceStorage().getDeviceForWidget(appWidgetId); - if (device == null || !device.isInitialized()) { + if (deviceForWidget == null || !deviceForWidget.isInitialized()) { GB.toast(context, context.getString(R.string.device_not_connected), Toast.LENGTH_SHORT, GB.ERROR); @@ -265,7 +241,7 @@ public class Widget extends AppWidgetProvider { super.onReceive(context, intent); LOG.debug("gbwidget LOCAL onReceive, action: " + intent.getAction() + intent); Bundle extras = intent.getExtras(); - int appWidgetId = -1; + int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; if (extras != null) { appWidgetId = extras.getInt( AppWidgetManager.EXTRA_APPWIDGET_ID, @@ -277,7 +253,7 @@ public class Widget extends AppWidgetProvider { if (broadcastReceiver == null) { onEnabled(context); } - refreshData(); + refreshData(appWidgetId); //updateWidget(); } else if (APPWIDGET_DELETED.equals(intent.getAction())) { onDisabled(context); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepAlarmWidgetConfigurationActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepAlarmWidgetConfigurationActivity.java new file mode 100644 index 0000000000..a2e6235493 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepAlarmWidgetConfigurationActivity.java @@ -0,0 +1,139 @@ +package nodomain.freeyourgadget.gadgetbridge.activities; + +import android.app.Activity; +import android.app.AlertDialog; +import android.appwidget.AppWidgetManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.util.Pair; +import android.widget.ListView; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +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.devices.DeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.Device; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; +import nodomain.freeyourgadget.gadgetbridge.util.WidgetPreferenceStorage; + +public class SleepAlarmWidgetConfigurationActivity extends Activity { + + // modified copy of WidgetConfigurationActivity + // if we knew which widget is calling this config activity, we could only use a single configuration + // activity and customize the filter in getAllDevices based on the caller. + + private static final Logger LOG = LoggerFactory.getLogger(SleepAlarmWidgetConfigurationActivity.class); + int mAppWidgetId; + + LinkedHashMap> allDevices; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setResult(RESULT_CANCELED); + + Intent intent = getIntent(); + Bundle extras = intent.getExtras(); + + if (extras != null) { + mAppWidgetId = extras.getInt( + AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID); + } + // make the result intent and set the result to canceled + Intent resultValue; + resultValue = new Intent(); + resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); + setResult(RESULT_CANCELED, resultValue); + + if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { + finish(); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(SleepAlarmWidgetConfigurationActivity.this); + builder.setTitle(R.string.widget_settings_select_device_title); + + allDevices = getAllDevices(getApplicationContext()); + + List list = new ArrayList<>(); + for (Map.Entry> item : allDevices.entrySet()) { + list.add(item.getKey()); + } + String[] allDevicesString = list.toArray(new String[0]); + + builder.setSingleChoiceItems(allDevicesString, 0, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ListView lw = ((AlertDialog) dialog).getListView(); + int selectedItemPosition = lw.getCheckedItemPosition(); + + if (selectedItemPosition > -1) { + Map.Entry> selectedItem = + (Map.Entry>) allDevices.entrySet().toArray()[selectedItemPosition]; + WidgetPreferenceStorage widgetPreferenceStorage = new WidgetPreferenceStorage(); + widgetPreferenceStorage.saveWidgetPrefs(getApplicationContext(), String.valueOf(mAppWidgetId), selectedItem.getValue().first); + } + Intent resultValue = new Intent(); + resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); + setResult(RESULT_OK, resultValue); + finish(); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent resultValue; + resultValue = new Intent(); + resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); + setResult(RESULT_CANCELED, resultValue); + finish(); + } + }); + AlertDialog dialog = builder.create(); + dialog.show(); + } + + public LinkedHashMap getAllDevices(Context appContext) { + DaoSession daoSession; + GBApplication gbApp = (GBApplication) appContext; + LinkedHashMap> newMap = new LinkedHashMap<>(1); + List devices = gbApp.getDeviceManager().getDevices(); + + try (DBHandler handler = GBApplication.acquireDB()) { + daoSession = handler.getDaoSession(); + for (GBDevice device : devices) { + DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); + Device dbDevice = DBHelper.findDevice(device, daoSession); + int icon = device.isInitialized() ? device.getType().getIcon() : device.getType().getDisabledIcon(); + if (dbDevice != null && coordinator != null + && (coordinator.getAlarmSlotCount() > 0) + && !newMap.containsKey(device.getAliasOrName())) { + newMap.put(device.getAliasOrName(), new Pair(device.getAddress(), icon)); + } + } + } catch (Exception e) { + LOG.error("Error getting list of all devices: " + e); + } + return newMap; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetAlarmsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetAlarmsActivity.java index 59ee7ff218..f078a6f218 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetAlarmsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetAlarmsActivity.java @@ -40,17 +40,17 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB; public class WidgetAlarmsActivity extends Activity implements View.OnClickListener { TextView textView; + GBDevice deviceForWidget; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Context appContext = this.getApplicationContext(); - GBDevice selectedDevice; Bundle extras = getIntent().getExtras(); if (extras != null) { - selectedDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE); + deviceForWidget = extras.getParcelable(GBDevice.EXTRA_DEVICE); } else { GB.toast(this, "Error no device", @@ -59,9 +59,8 @@ public class WidgetAlarmsActivity extends Activity implements View.OnClickListen } if (appContext instanceof GBApplication) { - GBApplication gbApp = (GBApplication) appContext; - if (selectedDevice == null || !selectedDevice.isInitialized()) { + if (deviceForWidget == null || !deviceForWidget.isInitialized()) { GB.toast(this, this.getString(R.string.not_connected), Toast.LENGTH_LONG, GB.INFO); @@ -128,16 +127,11 @@ public class WidgetAlarmsActivity extends Activity implements View.OnClickListen // overwrite the first alarm and activate it, without - Context appContext = this.getApplicationContext(); - if (appContext instanceof GBApplication) { - GBApplication gbApp = (GBApplication) appContext; - GBDevice selectedDevice = gbApp.getDeviceManager().getSelectedDevice(); - if (selectedDevice == null || !selectedDevice.isInitialized()) { - GB.toast(this, - this.getString(R.string.appwidget_not_connected), - Toast.LENGTH_LONG, GB.WARN); - return; - } + if (deviceForWidget == null || !deviceForWidget.isInitialized()) { + GB.toast(this, + this.getString(R.string.appwidget_not_connected), + Toast.LENGTH_LONG, GB.WARN); + return; } int hours = calendar.get(Calendar.HOUR_OF_DAY); @@ -152,6 +146,5 @@ public class WidgetAlarmsActivity extends Activity implements View.OnClickListen alarms.add(alarm); GBApplication.deviceService().onSetAlarms(alarms); - } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetConfigurationActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetConfigurationActivity.java index 8ffea8c00e..b89d83ce9c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetConfigurationActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/WidgetConfigurationActivity.java @@ -31,7 +31,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.WidgetPreferenceStorage; public class WidgetConfigurationActivity extends Activity { - private static final Logger LOG = LoggerFactory.getLogger(Widget.class); + private static final Logger LOG = LoggerFactory.getLogger(WidgetConfigurationActivity.class); int mAppWidgetId; LinkedHashMap> allDevices; @@ -44,6 +44,7 @@ public class WidgetConfigurationActivity extends Activity { Intent intent = getIntent(); Bundle extras = intent.getExtras(); + if (extras != null) { mAppWidgetId = extras.getInt( AppWidgetManager.EXTRA_APPWIDGET_ID, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WidgetPreferenceStorage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WidgetPreferenceStorage.java index bcbdf8fb78..efcd7c807a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WidgetPreferenceStorage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WidgetPreferenceStorage.java @@ -26,7 +26,11 @@ import org.json.JSONException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.Widget; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; public class WidgetPreferenceStorage { private static final Logger LOG = LoggerFactory.getLogger(WidgetPreferenceStorage.class); @@ -151,4 +155,31 @@ public class WidgetPreferenceStorage { } GB.toast("Saved app widget preferences: " + savedWidgetsPreferencesDataArray, Toast.LENGTH_SHORT, GB.INFO); } + + public GBDevice getDeviceForWidget(int appWidgetId) { + Context context = GBApplication.getContext(); + if (!(context instanceof GBApplication)) { + return null; + } + + String savedDeviceAddress = getSavedDeviceAddress(context, appWidgetId); + + if (savedDeviceAddress != null) { + return getDeviceByMAC(context.getApplicationContext(), savedDeviceAddress); //this would probably only happen if device no longer exists in GB + } + return null; + } + + private GBDevice getDeviceByMAC(Context appContext, String HwAddress) { + GBApplication gbApp = (GBApplication) appContext; + List devices = gbApp.getDeviceManager().getDevices(); + for (GBDevice device : devices) { + if (device.getAddress().equals(HwAddress)) { + return device; + } + } + return null; + } + + } diff --git a/app/src/main/res/xml/sleep_alarm_widget_info.xml b/app/src/main/res/xml/sleep_alarm_widget_info.xml index c65c4ca383..dcf5c6ab12 100644 --- a/app/src/main/res/xml/sleep_alarm_widget_info.xml +++ b/app/src/main/res/xml/sleep_alarm_widget_info.xml @@ -6,4 +6,6 @@ android:minWidth="40dp" android:previewImage="@drawable/ic_launcher" android:updatePeriodMillis="86400000" - android:widgetCategory="home_screen"> \ No newline at end of file + android:configure="nodomain.freeyourgadget.gadgetbridge.activities.SleepAlarmWidgetConfigurationActivity" + android:widgetCategory="home_screen"> + \ No newline at end of file From 22a9ad329ed624738768909e5bdb0f6c18eeda63 Mon Sep 17 00:00:00 2001 From: vanous Date: Sun, 29 May 2022 21:18:53 +0200 Subject: [PATCH 03/31] FitPro: fix crash, inactivity warning preference to string, after type enforcement in 3b348a5d5 --- .../gadgetbridge/GBApplication.java | 21 ++++++++++++++++++- .../DeviceSettingsPreferenceConst.java | 1 + .../DeviceSpecificSettingsFragment.java | 1 + .../devices/fitpro/FitProDeviceSupport.java | 4 ++-- .../devicesettings_inactivity_extended.xml | 2 +- 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 085370cb4f..24aae687ba 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -117,7 +117,7 @@ public class GBApplication extends Application { private static SharedPreferences sharedPrefs; private static final String PREFS_VERSION = "shared_preferences_version"; //if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version - private static final int CURRENT_PREFS_VERSION = 14; + private static final int CURRENT_PREFS_VERSION = 15; private static LimitedQueue mIDSenderLookup = new LimitedQueue(16); private static Prefs prefs; @@ -1143,6 +1143,25 @@ public class GBApplication extends Application { } } + if (oldVersion < 15) { + try (DBHandler db = acquireDB()) { + final DaoSession daoSession = db.getDaoSession(); + final List activeDevices = DBHelper.getActiveDevices(daoSession); + + for (Device dbDevice : activeDevices) { + final SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier()); + final SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit(); + + if (DeviceType.FITPRO.equals(dbDevice.getType())) { + editor.remove("inactivity_warnings_threshold"); + deviceSharedPrefsEdit.apply(); + } + } + } catch (Exception e) { + Log.w(TAG, "error acquiring DB lock"); + } + } + editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION)); editor.apply(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index 495a92f7ca..36f6638a91 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -77,6 +77,7 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_INACTIVITY_START = "inactivity_warnings_start"; public static final String PREF_INACTIVITY_END = "inactivity_warnings_end"; public static final String PREF_INACTIVITY_THRESHOLD = "inactivity_warnings_threshold"; + public static final String PREF_INACTIVITY_THRESHOLD_EXTENDED = "inactivity_warnings_threshold_extended"; public static final String PREF_INACTIVITY_MO = "inactivity_warnings_mo"; public static final String PREF_INACTIVITY_TU = "inactivity_warnings_tu"; public static final String PREF_INACTIVITY_WE = "inactivity_warnings_we"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java index efe23cbca0..53808daff5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java @@ -429,6 +429,7 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp addPreferenceHandlerFor(PREF_INACTIVITY_START); addPreferenceHandlerFor(PREF_INACTIVITY_END); addPreferenceHandlerFor(PREF_INACTIVITY_THRESHOLD); + addPreferenceHandlerFor(PREF_INACTIVITY_THRESHOLD_EXTENDED); addPreferenceHandlerFor(PREF_INACTIVITY_MO); addPreferenceHandlerFor(PREF_INACTIVITY_TU); addPreferenceHandlerFor(PREF_INACTIVITY_WE); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java index 63596edfa4..a35bfabca9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java @@ -543,7 +543,7 @@ public class FitProDeviceSupport extends AbstractBTLEDeviceSupport { case DeviceSettingsPreferenceConst.PREF_LANGUAGE: setLanguage(builder); break; - case DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD: + case DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD_EXTENDED: case DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE: case DeviceSettingsPreferenceConst.PREF_INACTIVITY_START: case DeviceSettingsPreferenceConst.PREF_INACTIVITY_END: @@ -1148,7 +1148,7 @@ public class FitProDeviceSupport extends AbstractBTLEDeviceSupport { if (prefLongsitSwitch) { - String inactivity = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD, "4"); + String inactivity = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD_EXTENDED, "4"); String start = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_START, "08:00"); String end = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_INACTIVITY_END, "16:00"); Calendar startCalendar = GregorianCalendar.getInstance(); diff --git a/app/src/main/res/xml/devicesettings_inactivity_extended.xml b/app/src/main/res/xml/devicesettings_inactivity_extended.xml index 7063b8572e..766e865ab4 100644 --- a/app/src/main/res/xml/devicesettings_inactivity_extended.xml +++ b/app/src/main/res/xml/devicesettings_inactivity_extended.xml @@ -21,7 +21,7 @@ android:dependency="inactivity_warnings_enable" android:entries="@array/inactivity_minutes" android:entryValues="@array/inactivity_minutes_values" - android:key="inactivity_warnings_threshold" + android:key="inactivity_warnings_threshold_extended" android:summary="@string/mi2_prefs_inactivity_warnings_summary" android:title="@string/mi2_prefs_inactivity_warnings_threshold" /> From 6f70204c9606f1101424588f590d1f0c427d0606 Mon Sep 17 00:00:00 2001 From: vanous Date: Fri, 20 May 2022 10:23:55 +0000 Subject: [PATCH 04/31] Translated using Weblate (Czech) Currently translated at 100.0% (1583 of 1583 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/cs/ --- app/src/main/res/values-cs/strings.xml | 75 +++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index c86c56640a..5b844036f4 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1023,7 +1023,7 @@ km sec/m Jóga - Skákací lano + Švihadlo Eliptický trenažér Sálová Cyklistika Plavání (otevřená voda) @@ -1641,4 +1641,77 @@ Konfigurace hodin pro jiná časová pásma Odstranit \'%1$s\' Žádné volné místo + Sony WF-1000XM3 + Galaxy Buds Pro + Okolní zvuk + Povolit přístup k internetu + Povolit všem aplikacím na hodinkách přístup k internetu + Povolit Intenty + 105 tepů/min + 100 tepů/min + 110 tepů/min + 112 tepů/min + Ovládání přepínání Vpravo + Hlasitost + Spotify + Přepínání regulace hluku + %1$s potřebuje přístup k Notifikacím, aby bylo možno notifikace zobrazovat i na připojených hodinkách/náramku. +\n +\nZvolte \'%2$s\', poté \'%1$s\' a a vyberte \'Povolit přístup k Notifikacím\', poté zvolte \'Zpět\' pro návrat do %1$s + %1$s potřebuje přístup k funkci Nerušit, aby bylo možno nastavit Nerušit i na připojených hodinkách/náramku. +\n +\nZvolte \'%2$s\', poté \'%1$s\' a a vyberte \'Povolit přístup funkci Nerušit\', poté zvolte \'Zpět\' pro návrat do %1$s + Přehrávání hovorů do sluchátek, jsou li v uších + 130 tepů/min + Text jako Bitmapa + Pokud není možno zobrazit dané slovo pomocí fontu hodinek, Gadgetbridge vykreslí obrázek, který se na hodinkách zobrazí + 135 tepů/min + 150 tepů/min + Sledování stresu + Automaticky povolit okolní zvuk a snížit přehrávání po zjištění hlasu + Umožní aplikacím Bangle.js hodinek posílat Android Intenty a povolí ostatním Android aplikacím (Tasker) aby posílaly data do Bangle.js pomocí com.banglejs.uart.tx Intentu. + Zavibruje řemínkem, pokud tepová frekvence překročí prahovou hodnotu bez zjevné fyzické aktivity v posledních 10 minutách. Tato funkce je experimentální a nebyla důkladně testována. + Automaticky zvýší četnost detekce srdeční frekvence při fyzické aktivitě. + Monitorování srdeční frekvence + Okolní zvuk během hovoru + Ovládání přepínání Vlevo + Okolní zvuk ←→ Vypnuto + 120 tepů/min + 140 tepů/min + 125 tepů/min + Upozornění na srdeční frekvenci (experimentální) + Sledování úrovně stresu při odpočinku + 145 tepů/min + Prahová hodnota upozornění na srdeční frekvenci + Sledování aktivity + Konfigurace monitorování srdečního tepu + Konfigurace monitorování srdečního tepu a prahových hodnot výstrah + Zjednodušené přepínání připojení + Přepíná sluchátka automaticky mezi připojenými zařízeními + Okolní hlasitost Vlevo + Okolní hlasitost Vpravo + Přizpůsobení nastavení okolního zvuku + Během hovoru slyšet vlastní hlas + Možnosti okolního zvuku + Úroveň aktivního potlačení hluku + Vysoká + Aktivní potlačení hluku + Potlačení hluku ←→ Vypnuto + Konec po klidu za: + Nízká + Rychlý okolní zvuk + Regulace hluku s jedním sluchátkem + Od Měkkého po Jasný + Vyrovnání zvuku + Potlačení hluku ←→ Okolní zvuk + Regulace hluku + Detekce hlasu + Hlasový asistent + Povolit regulaci hluku i při použití pouze s jednoho sluchátka + Tón okolního zvuku + Dvojité klepnutí na okraj + Detekce dvojitého klepnutí, i když není klepnuto na dotykový panel + 5 sekund + 15 sekund + 10 sekund \ No newline at end of file From a61ac4852c09f22e016ad9065099375edc870c97 Mon Sep 17 00:00:00 2001 From: nautilusx Date: Thu, 19 May 2022 20:51:45 +0000 Subject: [PATCH 05/31] Translated using Weblate (German) Currently translated at 94.4% (1495 of 1583 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/de/ --- app/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 0da11a7440..06f9c18330 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1627,4 +1627,5 @@ Bangle.js läuft Über Bangle.js Gadgetbridge Fitness-App-Tracking umschalten + Text als Bitmaps \ No newline at end of file From cea7affdbc8e14b6bebddb5321b2a60e6a1c75b3 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Fri, 20 May 2022 12:59:23 +0000 Subject: [PATCH 06/31] Translated using Weblate (Hebrew) Currently translated at 99.8% (1579 of 1581 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/he/ --- app/src/main/res/values-he/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 4863b53403..1a9a263309 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1699,4 +1699,8 @@ צליל הקפי במהלך שיחה לזהות נגיעה כפולה כשבוצעה בקצוות משטח המגע להפעיל את הרטט בצמיד כאשר הדופק יורד מתחת לסף, ללא פעילות פיזית מובהקת ב־10 הדקות האחרונות. יכולת זאת נמצאת בשלבי ניסוי ולא נבדקה באופן קפדני. + טקסט כמפת סיביות + לאפשר גישה לאינטרנט + לאפשר ליישומונים במכשיר הזה לגשת לאינטרנט + אם אי אפשר לעבד תמונות עם גופן השעון, הוא יומר למפת סיביות ב־Gadgetbridge שתוצג בשעון \ No newline at end of file From 80747611ef7fcd4cd4f199d7370cffcdc6553d3a Mon Sep 17 00:00:00 2001 From: Michal L Date: Sat, 21 May 2022 14:47:00 +0000 Subject: [PATCH 07/31] Translated using Weblate (Polish) Currently translated at 95.0% (1502 of 1581 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/pl/ --- app/src/main/res/values-pl/strings.xml | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 28eea76cc1..5290f29e2e 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1606,4 +1606,39 @@ Lokalizacja nie jest dostępna. Najprawdopodobniej jest to spowodowane nowym systemem uprawnień Androida. Prawdopodobnie automatyczny eksport teraz nie działa. Podświetlanie nowych powiadomień E-mail + Znajdź urządzenie + Przypomnienie o wydarzeniach + Alerty o bezczynności + Wzory wibracji + Skonfiguruj wzory wibracji dla różnych powiadomień + Sony WF-1000XM3 + Galaxy Buds Pro + O Bangle.js Gadgetbridge + Bangle.js Gadgetbridge + Bangle.js Gadgetbridge + Bangle.js jest uruchomiony + O Bangle.js Gadgetbridge + Bangle.js Gadgetbridge + Bangle.js Gadgetbridge + Bangle.js jest uruchomiony + Tekst jako bitmapy + Zezwól na dostęp do internetu + Zezwól aplikacjom na tym urządzeniu na dostęp do Internetu + 100 bpm + Usuń \'%1$s\' + Strefa czasowa + 105 bpm + 110 bpm + 112 bpm + 120 bpm + 125 bpm + 130 bpm + 135 bpm + 140 bpm + 145 bpm + 150 bpm + Spotify + 5 sekund + 10 sekund + 15 sekund \ No newline at end of file From c44af92502986bead44a833838a35ffe9b9db202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Thu, 19 May 2022 20:40:24 +0000 Subject: [PATCH 08/31] Translated using Weblate (Turkish) Currently translated at 100.0% (1581 of 1581 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/tr/ --- app/src/main/res/values-tr/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index b7e170fd93..98c6e557fa 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1716,4 +1716,10 @@ Kalp ritmi izlemeyi ve uyarı eşiklerini yapılandırın Kulaklıkları eşleştirilen aygıtlar arasında otomatik olarak değiştirir Ortam Ses Seviyesi Sağ + Bit Eşlem Olarak Metin + Bangle.js saat uygulamalarının Android Amaçları göndermesine ve Android\'deki diğer uygulamaların (Tasker gibi) com.banglejs.uart.tx Niyeti ile Bangle.js\'ye veri göndermesine izin ver. + Amaçlara İzin Ver + Bu aygıttaki uygulamaların internete erişmesine izin ver + Bir sözcük saatin yazı tipi kullanılarak görüntülenemiyorsa, onu Gadgetbridge\'de bir bit eşleme dönüştür ve bit eşlemi saatte görüntüle + İnternet Erişimine İzin Ver \ No newline at end of file From ca990edec45fb4acb07402f7c2d8360dd72d4ea5 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Thu, 19 May 2022 22:05:07 +0000 Subject: [PATCH 09/31] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1581 of 1581 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/uk/ --- app/src/main/res/values-uk/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 03c8d82c66..fe6f646097 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1707,4 +1707,10 @@ Налаштуйте пороги стеження за частотою серцевих скорочень та попереджень Стеження за частотою серцевих скорочень Налаштувати стеження за частотою серцевих скорочень + Дозволити доступ до інтернету + Дозволити застосункам на цьому пристрої доступ до інтернету + Дозволити застосункам для годинника Bangle.js надсилати наміри Android і дозволити іншим застосункам на Android (наприклад, Tasker) надсилати дані до Bangle.js із наміром com.banglejs.uart.tx. + Дозволити наміри + Текст у вигляді растрових зображень + Якщо слово не може бути відтворено шрифтом годинника, перетворювати його на растрове зображення в Gadgetbridge і показувати растрове зображення на годиннику \ No newline at end of file From f05368e2c038d65cb588278bcf72ba1c46161cbe Mon Sep 17 00:00:00 2001 From: arjan-s Date: Fri, 20 May 2022 19:20:22 +0000 Subject: [PATCH 10/31] Translated using Weblate (Dutch) Currently translated at 100.0% (1581 of 1581 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/nl/ --- app/src/main/res/values-nl/strings.xml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 3c6de9d589..62ae797938 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -9,12 +9,12 @@ Synchroniseer Zoek verloren apparaat Screenshot maken - Ontkoppel + Verbinding verbreken Verwijder apparaat Verwijder %1$s Dit zal het apparaat en alle bijbehorende gegevens verwijderen! - Druk lang op de kaart om te ontkoppelen - Ontkoppelen + Druk lang op de kaart om de verbinding te verbreken + Verbinding verbreken Verbinden… Een screenshot maken van het apparaat Debug @@ -1698,4 +1698,12 @@ 150 bpm 135 bpm 140 bpm + Apps op dit apparaat toegang geven tot internet + Sta toe dat Bangle.js apps Android-intents verzenden en sta andere apps op Android (zoals Tasker) toe om gegevens naar Bangle.js te verzenden met de com.banglejs.uart.tx-intent. + Tekst als afbeelding + Internettoegang toestaan + Als een woord niet kan worden weergegeven met het lettertype van het horloge, render het dan naar een afbeelding in Gadgetbridge en toon de afbeelding op het horloge + Intents toestaan + Bediening links + Bediening rechts \ No newline at end of file From 744f3c9b9fad4936b5caca6c8cd69fddd3ae0c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B0=91=E4=B8=BE?= Date: Fri, 20 May 2022 02:00:38 +0000 Subject: [PATCH 11/31] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1581 of 1581 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c410569202..c6fc36a9f7 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1705,4 +1705,10 @@ 心率监测 配置心率监测 配置心率监测和警报阈值 + 作为位图的文本 + 如果无法使用手表的字体呈现单词,请将其呈现为 Gadgetbridge 中的位图,并在手表上显示位图 + 允许 Bangle.js 手表应用发送 Android 意图,并允许 Android 上的其他应用(如 Tasker)使用 com.banglejs.uart.tx 意图向 Bangle.js 发送数据。 + 允许意向 + 允许互联网访问 + 允许此设备上的应用访问互联网 \ No newline at end of file From 49bc868eb31a8e01d9324a450b2ebc1ebaaee5a5 Mon Sep 17 00:00:00 2001 From: tomechio Date: Thu, 26 May 2022 10:12:10 +0000 Subject: [PATCH 12/31] Translated using Weblate (Finnish) Currently translated at 25.5% (404 of 1581 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fi/ --- app/src/main/res/values-fi/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 820f5adff1..bf3db04038 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -474,4 +474,11 @@ VESC Leveys: Aikavyöhyke: + automaattinen + tunnista soutaminen + tunnista juokseminen + Valikko + tunnista käveleminen + kysy + tunnista pyöräily \ No newline at end of file From 90145ce97702d4b17b596f7d158f96a54d2ae125 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Fri, 27 May 2022 20:13:06 +0000 Subject: [PATCH 13/31] Translated using Weblate (Hebrew) Currently translated at 100.0% (1581 of 1581 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/he/ --- app/src/main/res/values-he/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 1a9a263309..bf4735689b 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1703,4 +1703,6 @@ לאפשר גישה לאינטרנט לאפשר ליישומונים במכשיר הזה לגשת לאינטרנט אם אי אפשר לעבד תמונות עם גופן השעון, הוא יומר למפת סיביות ב־Gadgetbridge שתוצג בשעון + לאפשר ליישומוני שעון של Bangle.js לשלוח Intents ל־Android ולאפשר ליישומי Android אחרים (כמו Tasker) לשלוח נתונים ל־Bangle.js באמצעות ה־Intent ‏com.banglejs.uart.tx. + לאפשר Intents \ No newline at end of file From b07dc6f2b2b326aef37bb0427b08f6ef9ecfd183 Mon Sep 17 00:00:00 2001 From: Ludovic Jozeau Date: Fri, 27 May 2022 00:49:50 +0200 Subject: [PATCH 14/31] fix calendar blacklist, view and storage - view: unselect calendar that aren't blacklisted - use more unique string to identify and store blacklisted calendars --- .../gadgetbridge/GBApplication.java | 16 +++++---- .../activities/CalBlacklistActivity.java | 20 ++++++++--- .../gadgetbridge/model/CalendarEvents.java | 33 ++++++++++++++++--- .../main/res/layout/item_cal_blacklist.xml | 14 ++++++-- .../gadgetbridge/test/CalendarEventTest.java | 17 ++++++---- 5 files changed, 77 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 24aae687ba..6f41b546ac 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -559,11 +559,11 @@ public class GBApplication extends Application { private static HashSet calendars_blacklist = null; - public static boolean calendarIsBlacklisted(String calendarDisplayName) { + public static boolean calendarIsBlacklisted(String calendarUniqueName) { if (calendars_blacklist == null) { GB.log("calendarIsBlacklisted: calendars_blacklist is null!", GB.INFO, null); } - return calendars_blacklist != null && calendars_blacklist.contains(calendarDisplayName); + return calendars_blacklist != null && calendars_blacklist.contains(calendarUniqueName); } public static void setCalendarsBlackList(Set calendarNames) { @@ -577,14 +577,18 @@ public class GBApplication extends Application { saveCalendarsBlackList(); } - public static void addCalendarToBlacklist(String calendarDisplayName) { - if (calendars_blacklist.add(calendarDisplayName)) { + public static void addCalendarToBlacklist(String calendarUniqueName) { + if (calendars_blacklist.add(calendarUniqueName)) { + GB.log("Blacklisted calendar " + calendarUniqueName, GB.INFO, null); saveCalendarsBlackList(); + } else { + GB.log("Calendar " + calendarUniqueName + " already blacklisted!", GB.WARN, null); } } - public static void removeFromCalendarBlacklist(String calendarDisplayName) { - calendars_blacklist.remove(calendarDisplayName); + public static void removeFromCalendarBlacklist(String calendarUniqueName) { + calendars_blacklist.remove(calendarUniqueName); + GB.log("Unblacklisted calendar " + calendarUniqueName, GB.INFO, null); saveCalendarsBlackList(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CalBlacklistActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CalBlacklistActivity.java index cb2744ab89..cfdf1afbf8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CalBlacklistActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/CalBlacklistActivity.java @@ -51,6 +51,7 @@ public class CalBlacklistActivity extends AbstractGBActivity { private final String[] EVENT_PROJECTION = new String[]{ CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, + CalendarContract.Calendars.ACCOUNT_NAME, CalendarContract.Calendars.CALENDAR_COLOR }; private ArrayList calendarsArrayList; @@ -69,7 +70,7 @@ public class CalBlacklistActivity extends AbstractGBActivity { try (Cursor cur = getContentResolver().query(uri, EVENT_PROJECTION, null, null, null)) { calendarsArrayList = new ArrayList<>(); while (cur != null && cur.moveToNext()) { - calendarsArrayList.add(new Calendar(cur.getString(0), cur.getInt(1))); + calendarsArrayList.add(new Calendar(cur.getString(0), cur.getString(1), cur.getInt(2))); } } @@ -82,9 +83,9 @@ public class CalBlacklistActivity extends AbstractGBActivity { CheckBox selected = (CheckBox) view.findViewById(R.id.item_checkbox); toggleEntry(view); if (selected.isChecked()) { - GBApplication.addCalendarToBlacklist(item.displayName); + GBApplication.addCalendarToBlacklist(item.getUniqueString()); } else { - GBApplication.removeFromCalendarBlacklist(item.displayName); + GBApplication.removeFromCalendarBlacklist(item.getUniqueString()); } } }); @@ -112,12 +113,18 @@ public class CalBlacklistActivity extends AbstractGBActivity { class Calendar { private final String displayName; + private final String accountName; private final int color; - public Calendar(String displayName, int color) { + public Calendar(String displayName, String accountName, int color) { this.displayName = displayName; + this.accountName = accountName; this.color = color; } + + public String getUniqueString() { + return accountName + '/' + displayName; + } } private class CalendarListAdapter extends ArrayAdapter { @@ -138,13 +145,16 @@ public class CalBlacklistActivity extends AbstractGBActivity { View color = view.findViewById(R.id.calendar_color); TextView name = (TextView) view.findViewById(R.id.calendar_name); + TextView ownerAccount = (TextView) view.findViewById(R.id.calendar_owner_account); CheckBox checked = (CheckBox) view.findViewById(R.id.item_checkbox); - if (GBApplication.calendarIsBlacklisted(item.displayName) && !checked.isChecked()) { + if (GBApplication.calendarIsBlacklisted(item.getUniqueString()) && !checked.isChecked() || + !GBApplication.calendarIsBlacklisted(item.getUniqueString()) && checked.isChecked()) { toggleEntry(view); } color.setBackgroundColor(item.color); name.setText(item.displayName); + ownerAccount.setText(item.accountName); return view; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEvents.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEvents.java index 9580bfc01c..776ac299b4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEvents.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEvents.java @@ -21,6 +21,7 @@ import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; +import android.provider.CalendarContract; import android.provider.CalendarContract.Instances; import android.text.format.Time; @@ -56,6 +57,8 @@ public class CalendarEvents { Instances.DESCRIPTION, Instances.EVENT_LOCATION, Instances.CALENDAR_DISPLAY_NAME, + CalendarContract.Calendars.ACCOUNT_NAME, + Instances.CALENDAR_COLOR, Instances.ALL_DAY }; @@ -101,12 +104,14 @@ public class CalendarEvents { evtCursor.getString(5), evtCursor.getString(6), evtCursor.getString(7), - !evtCursor.getString(8).equals("0") + evtCursor.getString(8), + evtCursor.getInt(9), + !evtCursor.getString(10).equals("0") ); - if (!GBApplication.calendarIsBlacklisted(calEvent.getCalName())) { + if (!GBApplication.calendarIsBlacklisted(calEvent.getUniqueCalName())) { calendarEventList.add(calEvent); } else { - LOG.debug("calendar " + calEvent.getCalName() + " skipped because it's blacklisted"); + LOG.debug("calendar " + calEvent.getUniqueCalName() + " skipped because it's blacklisted"); } } return true; @@ -124,9 +129,11 @@ public class CalendarEvents { private String description; private String location; private String calName; + private String calAccountName; + private int color; private boolean allDay; - public CalendarEvent(long begin, long end, long id, String title, String description, String location, String calName, boolean allDay) { + public CalendarEvent(long begin, long end, long id, String title, String description, String location, String calName, String calAccountName, int color, boolean allDay) { this.begin = begin; this.end = end; this.id = id; @@ -134,6 +141,8 @@ public class CalendarEvents { this.description = description; this.location = location; this.calName = calName; + this.calAccountName = calAccountName; + this.color = color; this.allDay = allDay; } @@ -182,6 +191,18 @@ public class CalendarEvents { return calName; } + public String getCalAccountName() { + return calAccountName; + } + + public String getUniqueCalName() { + return getCalAccountName() + '/' + getCalName(); + } + + public int getColor() { + return color; + } + public boolean isAllDay() { return allDay; } @@ -197,6 +218,8 @@ public class CalendarEvents { Objects.equals(this.getDescription(), e.getDescription()) && (this.getEnd() == e.getEnd()) && Objects.equals(this.getCalName(), e.getCalName()) && + Objects.equals(this.getCalAccountName(), e.getCalAccountName()) && + (this.getColor() == e.getColor()) && (this.isAllDay() == e.isAllDay()); } else { return false; @@ -212,6 +235,8 @@ public class CalendarEvents { result = 31 * result + Objects.hash(description); result = 31 * result + Long.valueOf(end).hashCode(); result = 31 * result + Objects.hash(calName); + result = 31 * result + Objects.hash(calAccountName); + result = 31 * result + Integer.valueOf(color).hashCode(); result = 31 * result + Boolean.valueOf(allDay).hashCode(); return result; } diff --git a/app/src/main/res/layout/item_cal_blacklist.xml b/app/src/main/res/layout/item_cal_blacklist.xml index 7de67c23c0..e26fccaa74 100644 --- a/app/src/main/res/layout/item_cal_blacklist.xml +++ b/app/src/main/res/layout/item_cal_blacklist.xml @@ -25,8 +25,8 @@ android:layout_marginStart="16dp" android:layout_marginTop="8dp" android:layout_toEndOf="@+id/item_checkbox" - android:paddingBottom="8dp" - android:paddingTop="8dp" /> + android:paddingTop="8dp" + android:paddingBottom="8dp" /> + + eventList = new ArrayList<>(); - eventList.add(new CalendarEvents.CalendarEvent(BEGIN, END, ID_1, null, "something", null, CALNAME_1, false)); + eventList.add(new CalendarEvents.CalendarEvent(BEGIN, END, ID_1, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false)); GBDevice dummyGBDevice = createDummyGDevice("00:00:01:00:03"); dummyGBDevice.setState(GBDevice.State.INITIALIZED); @@ -44,7 +49,7 @@ public class CalendarEventTest extends TestBase { testCR.syncCalendar(eventList); - eventList.add(new CalendarEvents.CalendarEvent(BEGIN, END, ID_2, null, "something", null, CALNAME_1, false)); + eventList.add(new CalendarEvents.CalendarEvent(BEGIN, END, ID_2, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false)); testCR.syncCalendar(eventList); CalendarSyncStateDao calendarSyncStateDao = daoSession.getCalendarSyncStateDao(); From dd30e6aa8a9c544d344dfe398351a4062d348df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sun, 22 May 2022 17:08:41 +0100 Subject: [PATCH 15/31] Extract Track and TrackStatistics from OpenTracksContentObserver --- .../OpenTracksContentObserver.java | 287 ------------------ .../gadgetbridge/externalevents/Track.java | 205 +++++++++++++ .../externalevents/TrackStatistics.java | 130 ++++++++ 3 files changed, 335 insertions(+), 287 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/Track.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TrackStatistics.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OpenTracksContentObserver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OpenTracksContentObserver.java index aee21b9dac..39751ba82c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OpenTracksContentObserver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OpenTracksContentObserver.java @@ -104,290 +104,3 @@ public class OpenTracksContentObserver extends ContentObserver { } } -class Track { - /** - * This class was copied and modified from - * https://github.com/OpenTracksApp/OSMDashboard/blob/main/src/main/java/de/storchp/opentracks/osmplugin/dashboardapi/Track.java - */ - private static final Logger LOG = LoggerFactory.getLogger(Track.class); - - private static final String TAG = Track.class.getSimpleName(); - - public static final String _ID = "_id"; - public static final String NAME = "name"; // track name - public static final String DESCRIPTION = "description"; // track description - public static final String CATEGORY = "category"; // track activity type - public static final String STARTTIME = "starttime"; // track start time - public static final String STOPTIME = "stoptime"; // track stop time - public static final String TOTALDISTANCE = "totaldistance"; // total distance - public static final String TOTALTIME = "totaltime"; // total time - public static final String MOVINGTIME = "movingtime"; // moving time - public static final String AVGSPEED = "avgspeed"; // average speed - public static final String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed - public static final String MAXSPEED = "maxspeed"; // maximum speed - public static final String MINELEVATION = "minelevation"; // minimum elevation - public static final String MAXELEVATION = "maxelevation"; // maximum elevation - public static final String ELEVATIONGAIN = "elevationgain"; // elevation gain - - public static final String[] PROJECTION = { - _ID, - NAME, - DESCRIPTION, - CATEGORY, - STARTTIME, - STOPTIME, - TOTALDISTANCE, - TOTALTIME, - MOVINGTIME, - AVGSPEED, - AVGMOVINGSPEED, - MAXSPEED, - MINELEVATION, - MAXELEVATION, - ELEVATIONGAIN - }; - - private final long id; - private final String trackname; - private final String description; - private final String category; - private final int startTimeEpochMillis; - private final int stopTimeEpochMillis; - private final float totalDistanceMeter; - private final int totalTimeMillis; - private final int movingTimeMillis; - private final float avgSpeedMeterPerSecond; - private final float avgMovingSpeedMeterPerSecond; - private final float maxSpeedMeterPerSecond; - private final float minElevationMeter; - private final float maxElevationMeter; - private final float elevationGainMeter; - - public Track(final long id, final String trackname, final String description, final String category, final int startTimeEpochMillis, final int stopTimeEpochMillis, final float totalDistanceMeter, final int totalTimeMillis, final int movingTimeMillis, final float avgSpeedMeterPerSecond, final float avgMovingSpeedMeterPerSecond, final float maxSpeedMeterPerSecond, final float minElevationMeter, final float maxElevationMeter, final float elevationGainMeter) { - this.id = id; - this.trackname = trackname; - this.description = description; - this.category = category; - this.startTimeEpochMillis = startTimeEpochMillis; - this.stopTimeEpochMillis = stopTimeEpochMillis; - this.totalDistanceMeter = totalDistanceMeter; - this.totalTimeMillis = totalTimeMillis; - this.movingTimeMillis = movingTimeMillis; - this.avgSpeedMeterPerSecond = avgSpeedMeterPerSecond; - this.avgMovingSpeedMeterPerSecond = avgMovingSpeedMeterPerSecond; - this.maxSpeedMeterPerSecond = maxSpeedMeterPerSecond; - this.minElevationMeter = minElevationMeter; - this.maxElevationMeter = maxElevationMeter; - this.elevationGainMeter = elevationGainMeter; - } - - /** - * Reads the Tracks from the Content Uri - */ - public static List readTracks(final ContentResolver resolver, final Uri data, final int protocolVersion) { - LOG.info("Loading track(s) from " + data); - - final ArrayList tracks = new ArrayList(); - try (final Cursor cursor = resolver.query(data, Track.PROJECTION, null, null, null)) { - while (cursor.moveToNext()) { - final long id = cursor.getLong(cursor.getColumnIndexOrThrow(Track._ID)); - final String trackname = cursor.getString(cursor.getColumnIndexOrThrow(Track.NAME)); - final String description = cursor.getString(cursor.getColumnIndexOrThrow(Track.DESCRIPTION)); - final String category = cursor.getString(cursor.getColumnIndexOrThrow(Track.CATEGORY)); - final int startTimeEpochMillis = cursor.getInt(cursor.getColumnIndexOrThrow(Track.STARTTIME)); - final int stopTimeEpochMillis = cursor.getInt(cursor.getColumnIndexOrThrow(Track.STOPTIME)); - final float totalDistanceMeter = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.TOTALDISTANCE)); - final int totalTimeMillis = cursor.getInt(cursor.getColumnIndexOrThrow(Track.TOTALTIME)); - final int movingTimeMillis = cursor.getInt(cursor.getColumnIndexOrThrow(Track.MOVINGTIME)); - final float avgSpeedMeterPerSecond = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.AVGSPEED)); - final float avgMovingSpeedMeterPerSecond = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.AVGMOVINGSPEED)); - final float maxSpeedMeterPerSecond = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.MAXSPEED)); - final float minElevationMeter = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.MINELEVATION)); - final float maxElevationMeter = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.MAXELEVATION)); - final float elevationGainMeter = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.ELEVATIONGAIN)); - - LOG.info("New Track data received: distance=" + totalDistanceMeter + " time=" + totalTimeMillis); - - tracks.add(new Track(id, trackname, description, category, startTimeEpochMillis, stopTimeEpochMillis, - totalDistanceMeter, totalTimeMillis, movingTimeMillis, avgSpeedMeterPerSecond, avgMovingSpeedMeterPerSecond, maxSpeedMeterPerSecond, - minElevationMeter, maxElevationMeter, elevationGainMeter)); - } - } catch (final SecurityException e) { - LOG.warn("No permission to read track", e); - } catch (final Exception e) { - LOG.warn("Reading track failed", e); - } - return tracks; - } - - public float getElevationGainMeter() { - return elevationGainMeter; - } - - public float getMaxElevationMeter() { - return maxElevationMeter; - } - - public float getMinElevationMeter() { - return minElevationMeter; - } - - public float getMaxSpeedMeterPerSecond() { - return maxSpeedMeterPerSecond; - } - - public float getAvgMovingSpeedMeterPerSecond() { - return avgMovingSpeedMeterPerSecond; - } - - public float getAvgSpeedMeterPerSecond() { - return avgSpeedMeterPerSecond; - } - - public int getMovingTimeMillis() { - return movingTimeMillis; - } - - public int getTotalTimeMillis() { - return totalTimeMillis; - } - - public float getTotalDistanceMeter() { - return totalDistanceMeter; - } - - public int getStopTimeEpochMillis() { - return stopTimeEpochMillis; - } - - public int getStartTimeEpochMillis() { - return startTimeEpochMillis; - } - - public String getCategory() { - return category; - } - - public String getDescription() { - return description; - } - - public String getTrackname() { - return trackname; - } - - public long getId() { - return id; - } -} - -class TrackStatistics { - /** - * This class was copied and modified from - * https://github.com/OpenTracksApp/OSMDashboard/blob/main/src/main/java/de/storchp/opentracks/osmplugin/utils/TrackStatistics.java - */ - - private String category = "unknown"; - private int startTimeEpochMillis; - private int stopTimeEpochMillis; - private float totalDistanceMeter; - private int totalTimeMillis; - private int movingTimeMillis; - private float avgSpeedMeterPerSecond; - private float avgMovingSpeedMeterPerSecond; - private float maxSpeedMeterPerSecond; - private float minElevationMeter; - private float maxElevationMeter; - private float elevationGainMeter; - - public TrackStatistics(final List tracks) { - if (tracks.isEmpty()) { - return; - } - final Track first = tracks.get(0); - category = first.getCategory(); - startTimeEpochMillis = first.getStartTimeEpochMillis(); - stopTimeEpochMillis = first.getStopTimeEpochMillis(); - totalDistanceMeter = first.getTotalDistanceMeter(); - totalTimeMillis = first.getTotalTimeMillis(); - movingTimeMillis = first.getMovingTimeMillis(); - avgSpeedMeterPerSecond = first.getAvgSpeedMeterPerSecond(); - avgMovingSpeedMeterPerSecond = first.getAvgMovingSpeedMeterPerSecond(); - maxSpeedMeterPerSecond = first.getMaxSpeedMeterPerSecond(); - minElevationMeter = first.getMinElevationMeter(); - maxElevationMeter = first.getMaxElevationMeter(); - elevationGainMeter = first.getElevationGainMeter(); - - if (tracks.size() > 1) { - float totalAvgSpeedMeterPerSecond = avgSpeedMeterPerSecond; - float totalAvgMovingSpeedMeterPerSecond = avgMovingSpeedMeterPerSecond; - for (final Track track : tracks.subList(1, tracks.size())) { - if (!category.equals(track.getCategory())) { - category = "mixed"; - } - startTimeEpochMillis = Math.min(startTimeEpochMillis, track.getStartTimeEpochMillis()); - stopTimeEpochMillis = Math.max(stopTimeEpochMillis, track.getStopTimeEpochMillis()); - totalDistanceMeter += track.getTotalDistanceMeter(); - totalTimeMillis += track.getTotalTimeMillis(); - movingTimeMillis += track.getMovingTimeMillis(); - totalAvgSpeedMeterPerSecond += track.getAvgSpeedMeterPerSecond(); - totalAvgMovingSpeedMeterPerSecond += track.getAvgMovingSpeedMeterPerSecond(); - maxSpeedMeterPerSecond = Math.max(maxSpeedMeterPerSecond, track.getMaxSpeedMeterPerSecond()); - minElevationMeter = Math.min(minElevationMeter, track.getMinElevationMeter()); - maxElevationMeter = Math.max(maxElevationMeter, track.getMaxElevationMeter()); - elevationGainMeter += track.getElevationGainMeter(); - } - - avgSpeedMeterPerSecond = totalAvgSpeedMeterPerSecond / tracks.size(); - avgMovingSpeedMeterPerSecond = totalAvgMovingSpeedMeterPerSecond / tracks.size(); - } - } - - public String getCategory() { - return category; - } - - public int getStartTimeEpochMillis() { - return startTimeEpochMillis; - } - - public int getStopTimeEpochMillis() { - return stopTimeEpochMillis; - } - - public float getTotalDistanceMeter() { - return totalDistanceMeter; - } - - public int getTotalTimeMillis() { - return totalTimeMillis; - } - - public int getMovingTimeMillis() { - return movingTimeMillis; - } - - public float getAvgSpeedMeterPerSecond() { - return avgSpeedMeterPerSecond; - } - - public float getAvgMovingSpeedMeterPerSecond() { - return avgMovingSpeedMeterPerSecond; - } - - public float getMaxSpeedMeterPerSecond() { - return maxSpeedMeterPerSecond; - } - - public float getMinElevationMeter() { - return minElevationMeter; - } - - public float getMaxElevationMeter() { - return maxElevationMeter; - } - - public float getElevationGainMeter() { - return elevationGainMeter; - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/Track.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/Track.java new file mode 100644 index 0000000000..a071d03e97 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/Track.java @@ -0,0 +1,205 @@ +/* Copyright (C) 2022 Arjan Schrijver + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ + +package nodomain.freeyourgadget.gadgetbridge.externalevents; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.net.Uri; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class was copied and modified from + * https://github.com/OpenTracksApp/OSMDashboard/blob/main/src/main/java/de/storchp/opentracks/osmplugin/dashboardapi/Track.java + */ +class Track { + private static final Logger LOG = LoggerFactory.getLogger(Track.class); + + private static final String TAG = Track.class.getSimpleName(); + + public static final String _ID = "_id"; + public static final String NAME = "name"; // track name + public static final String DESCRIPTION = "description"; // track description + public static final String CATEGORY = "category"; // track activity type + public static final String STARTTIME = "starttime"; // track start time + public static final String STOPTIME = "stoptime"; // track stop time + public static final String TOTALDISTANCE = "totaldistance"; // total distance + public static final String TOTALTIME = "totaltime"; // total time + public static final String MOVINGTIME = "movingtime"; // moving time + public static final String AVGSPEED = "avgspeed"; // average speed + public static final String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed + public static final String MAXSPEED = "maxspeed"; // maximum speed + public static final String MINELEVATION = "minelevation"; // minimum elevation + public static final String MAXELEVATION = "maxelevation"; // maximum elevation + public static final String ELEVATIONGAIN = "elevationgain"; // elevation gain + + public static final String[] PROJECTION = { + _ID, + NAME, + DESCRIPTION, + CATEGORY, + STARTTIME, + STOPTIME, + TOTALDISTANCE, + TOTALTIME, + MOVINGTIME, + AVGSPEED, + AVGMOVINGSPEED, + MAXSPEED, + MINELEVATION, + MAXELEVATION, + ELEVATIONGAIN + }; + + private final long id; + private final String trackname; + private final String description; + private final String category; + private final int startTimeEpochMillis; + private final int stopTimeEpochMillis; + private final float totalDistanceMeter; + private final int totalTimeMillis; + private final int movingTimeMillis; + private final float avgSpeedMeterPerSecond; + private final float avgMovingSpeedMeterPerSecond; + private final float maxSpeedMeterPerSecond; + private final float minElevationMeter; + private final float maxElevationMeter; + private final float elevationGainMeter; + + public Track(final long id, final String trackname, final String description, final String category, final int startTimeEpochMillis, final int stopTimeEpochMillis, final float totalDistanceMeter, final int totalTimeMillis, final int movingTimeMillis, final float avgSpeedMeterPerSecond, final float avgMovingSpeedMeterPerSecond, final float maxSpeedMeterPerSecond, final float minElevationMeter, final float maxElevationMeter, final float elevationGainMeter) { + this.id = id; + this.trackname = trackname; + this.description = description; + this.category = category; + this.startTimeEpochMillis = startTimeEpochMillis; + this.stopTimeEpochMillis = stopTimeEpochMillis; + this.totalDistanceMeter = totalDistanceMeter; + this.totalTimeMillis = totalTimeMillis; + this.movingTimeMillis = movingTimeMillis; + this.avgSpeedMeterPerSecond = avgSpeedMeterPerSecond; + this.avgMovingSpeedMeterPerSecond = avgMovingSpeedMeterPerSecond; + this.maxSpeedMeterPerSecond = maxSpeedMeterPerSecond; + this.minElevationMeter = minElevationMeter; + this.maxElevationMeter = maxElevationMeter; + this.elevationGainMeter = elevationGainMeter; + } + + /** + * Reads the Tracks from the Content Uri + */ + public static List readTracks(final ContentResolver resolver, final Uri data, final int protocolVersion) { + LOG.info("Loading track(s) from " + data); + + final ArrayList tracks = new ArrayList(); + try (final Cursor cursor = resolver.query(data, Track.PROJECTION, null, null, null)) { + while (cursor.moveToNext()) { + final long id = cursor.getLong(cursor.getColumnIndexOrThrow(Track._ID)); + final String trackname = cursor.getString(cursor.getColumnIndexOrThrow(Track.NAME)); + final String description = cursor.getString(cursor.getColumnIndexOrThrow(Track.DESCRIPTION)); + final String category = cursor.getString(cursor.getColumnIndexOrThrow(Track.CATEGORY)); + final int startTimeEpochMillis = cursor.getInt(cursor.getColumnIndexOrThrow(Track.STARTTIME)); + final int stopTimeEpochMillis = cursor.getInt(cursor.getColumnIndexOrThrow(Track.STOPTIME)); + final float totalDistanceMeter = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.TOTALDISTANCE)); + final int totalTimeMillis = cursor.getInt(cursor.getColumnIndexOrThrow(Track.TOTALTIME)); + final int movingTimeMillis = cursor.getInt(cursor.getColumnIndexOrThrow(Track.MOVINGTIME)); + final float avgSpeedMeterPerSecond = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.AVGSPEED)); + final float avgMovingSpeedMeterPerSecond = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.AVGMOVINGSPEED)); + final float maxSpeedMeterPerSecond = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.MAXSPEED)); + final float minElevationMeter = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.MINELEVATION)); + final float maxElevationMeter = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.MAXELEVATION)); + final float elevationGainMeter = cursor.getFloat(cursor.getColumnIndexOrThrow(Track.ELEVATIONGAIN)); + + LOG.info("New Track data received: distance=" + totalDistanceMeter + " time=" + totalTimeMillis); + + tracks.add(new Track(id, trackname, description, category, startTimeEpochMillis, stopTimeEpochMillis, + totalDistanceMeter, totalTimeMillis, movingTimeMillis, avgSpeedMeterPerSecond, avgMovingSpeedMeterPerSecond, maxSpeedMeterPerSecond, + minElevationMeter, maxElevationMeter, elevationGainMeter)); + } + } catch (final SecurityException e) { + LOG.warn("No permission to read track", e); + } catch (final Exception e) { + LOG.warn("Reading track failed", e); + } + return tracks; + } + + public float getElevationGainMeter() { + return elevationGainMeter; + } + + public float getMaxElevationMeter() { + return maxElevationMeter; + } + + public float getMinElevationMeter() { + return minElevationMeter; + } + + public float getMaxSpeedMeterPerSecond() { + return maxSpeedMeterPerSecond; + } + + public float getAvgMovingSpeedMeterPerSecond() { + return avgMovingSpeedMeterPerSecond; + } + + public float getAvgSpeedMeterPerSecond() { + return avgSpeedMeterPerSecond; + } + + public int getMovingTimeMillis() { + return movingTimeMillis; + } + + public int getTotalTimeMillis() { + return totalTimeMillis; + } + + public float getTotalDistanceMeter() { + return totalDistanceMeter; + } + + public int getStopTimeEpochMillis() { + return stopTimeEpochMillis; + } + + public int getStartTimeEpochMillis() { + return startTimeEpochMillis; + } + + public String getCategory() { + return category; + } + + public String getDescription() { + return description; + } + + public String getTrackname() { + return trackname; + } + + public long getId() { + return id; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TrackStatistics.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TrackStatistics.java new file mode 100644 index 0000000000..82ffa00e7c --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TrackStatistics.java @@ -0,0 +1,130 @@ +/* Copyright (C) 2022 Arjan Schrijver + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ + +package nodomain.freeyourgadget.gadgetbridge.externalevents; + +import java.util.List; + +/** + * This class was copied and modified from + * https://github.com/OpenTracksApp/OSMDashboard/blob/main/src/main/java/de/storchp/opentracks/osmplugin/utils/TrackStatistics.java + */ +class TrackStatistics { + private String category = "unknown"; + private int startTimeEpochMillis; + private int stopTimeEpochMillis; + private float totalDistanceMeter; + private int totalTimeMillis; + private int movingTimeMillis; + private float avgSpeedMeterPerSecond; + private float avgMovingSpeedMeterPerSecond; + private float maxSpeedMeterPerSecond; + private float minElevationMeter; + private float maxElevationMeter; + private float elevationGainMeter; + + public TrackStatistics(final List tracks) { + if (tracks.isEmpty()) { + return; + } + final Track first = tracks.get(0); + category = first.getCategory(); + startTimeEpochMillis = first.getStartTimeEpochMillis(); + stopTimeEpochMillis = first.getStopTimeEpochMillis(); + totalDistanceMeter = first.getTotalDistanceMeter(); + totalTimeMillis = first.getTotalTimeMillis(); + movingTimeMillis = first.getMovingTimeMillis(); + avgSpeedMeterPerSecond = first.getAvgSpeedMeterPerSecond(); + avgMovingSpeedMeterPerSecond = first.getAvgMovingSpeedMeterPerSecond(); + maxSpeedMeterPerSecond = first.getMaxSpeedMeterPerSecond(); + minElevationMeter = first.getMinElevationMeter(); + maxElevationMeter = first.getMaxElevationMeter(); + elevationGainMeter = first.getElevationGainMeter(); + + if (tracks.size() > 1) { + float totalAvgSpeedMeterPerSecond = avgSpeedMeterPerSecond; + float totalAvgMovingSpeedMeterPerSecond = avgMovingSpeedMeterPerSecond; + for (final Track track : tracks.subList(1, tracks.size())) { + if (!category.equals(track.getCategory())) { + category = "mixed"; + } + startTimeEpochMillis = Math.min(startTimeEpochMillis, track.getStartTimeEpochMillis()); + stopTimeEpochMillis = Math.max(stopTimeEpochMillis, track.getStopTimeEpochMillis()); + totalDistanceMeter += track.getTotalDistanceMeter(); + totalTimeMillis += track.getTotalTimeMillis(); + movingTimeMillis += track.getMovingTimeMillis(); + totalAvgSpeedMeterPerSecond += track.getAvgSpeedMeterPerSecond(); + totalAvgMovingSpeedMeterPerSecond += track.getAvgMovingSpeedMeterPerSecond(); + maxSpeedMeterPerSecond = Math.max(maxSpeedMeterPerSecond, track.getMaxSpeedMeterPerSecond()); + minElevationMeter = Math.min(minElevationMeter, track.getMinElevationMeter()); + maxElevationMeter = Math.max(maxElevationMeter, track.getMaxElevationMeter()); + elevationGainMeter += track.getElevationGainMeter(); + } + + avgSpeedMeterPerSecond = totalAvgSpeedMeterPerSecond / tracks.size(); + avgMovingSpeedMeterPerSecond = totalAvgMovingSpeedMeterPerSecond / tracks.size(); + } + } + + public String getCategory() { + return category; + } + + public int getStartTimeEpochMillis() { + return startTimeEpochMillis; + } + + public int getStopTimeEpochMillis() { + return stopTimeEpochMillis; + } + + public float getTotalDistanceMeter() { + return totalDistanceMeter; + } + + public int getTotalTimeMillis() { + return totalTimeMillis; + } + + public int getMovingTimeMillis() { + return movingTimeMillis; + } + + public float getAvgSpeedMeterPerSecond() { + return avgSpeedMeterPerSecond; + } + + public float getAvgMovingSpeedMeterPerSecond() { + return avgMovingSpeedMeterPerSecond; + } + + public float getMaxSpeedMeterPerSecond() { + return maxSpeedMeterPerSecond; + } + + public float getMinElevationMeter() { + return minElevationMeter; + } + + public float getMaxElevationMeter() { + return maxElevationMeter; + } + + public float getElevationGainMeter() { + return elevationGainMeter; + } +} From a5a653dc34f4850e1e6c5656357ebf02736e94f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sun, 22 May 2022 17:11:45 +0100 Subject: [PATCH 16/31] Move OpenTracks external events to dedicated package --- app/src/main/AndroidManifest.xml | 2 +- .../freeyourgadget/gadgetbridge/GBApplication.java | 2 +- .../gadgetbridge/activities/DebugActivity.java | 6 ++---- .../{ => opentracks}/OpenTracksContentObserver.java | 8 +------- .../{ => opentracks}/OpenTracksController.java | 4 ++-- .../externalevents/{ => opentracks}/Track.java | 2 +- .../externalevents/{ => opentracks}/TrackStatistics.java | 2 +- .../gadgetbridge/service/devices/huami/HuamiSupport.java | 2 +- .../requests/fossil_hr/workout/WorkoutRequestHandler.java | 2 +- 9 files changed, 11 insertions(+), 19 deletions(-) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/{ => opentracks}/OpenTracksContentObserver.java (93%) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/{ => opentracks}/OpenTracksController.java (96%) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/{ => opentracks}/Track.java (99%) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/{ => opentracks}/TrackStatistics.java (98%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b1a42c75b6..120e6c7419 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -687,7 +687,7 @@ diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 6f41b546ac..82a6e166ac 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -68,7 +68,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothStateChangeReceiver; -import nodomain.freeyourgadget.gadgetbridge.externalevents.OpenTracksContentObserver; +import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksContentObserver; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; 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 29957ada4f..7c3c994690 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java @@ -71,8 +71,6 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Random; -import java.util.Timer; -import java.util.TimerTask; import java.util.TreeMap; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -86,8 +84,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; -import nodomain.freeyourgadget.gadgetbridge.externalevents.OpenTracksContentObserver; -import nodomain.freeyourgadget.gadgetbridge.externalevents.OpenTracksController; +import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksContentObserver; +import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OpenTracksContentObserver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksContentObserver.java similarity index 93% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OpenTracksContentObserver.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksContentObserver.java index 39751ba82c..cd2374338e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OpenTracksContentObserver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksContentObserver.java @@ -15,20 +15,14 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package nodomain.freeyourgadget.gadgetbridge.externalevents; +package nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks; import android.app.Activity; -import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; -import android.database.Cursor; import android.net.Uri; import android.os.Handler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OpenTracksController.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksController.java similarity index 96% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OpenTracksController.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksController.java index 93a6d98925..3f1f590a4b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/OpenTracksController.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/OpenTracksController.java @@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package nodomain.freeyourgadget.gadgetbridge.externalevents; +package nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks; import android.app.Activity; import android.content.Context; @@ -93,7 +93,7 @@ public class OpenTracksController extends Activity { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setClassName(packageName, className); intent.putExtra("STATS_TARGET_PACKAGE", context.getPackageName()); - intent.putExtra("STATS_TARGET_CLASS", "nodomain.freeyourgadget.gadgetbridge.externalevents.OpenTracksController"); + intent.putExtra("STATS_TARGET_CLASS", OpenTracksController.class.getName()); try { context.startActivity(intent); } catch (Exception e) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/Track.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/Track.java similarity index 99% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/Track.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/Track.java index a071d03e97..6b9032472a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/Track.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/Track.java @@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package nodomain.freeyourgadget.gadgetbridge.externalevents; +package nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks; import android.content.ContentResolver; import android.database.Cursor; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TrackStatistics.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/TrackStatistics.java similarity index 98% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TrackStatistics.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/TrackStatistics.java index 82ffa00e7c..3e40583b87 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TrackStatistics.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/opentracks/TrackStatistics.java @@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package nodomain.freeyourgadget.gadgetbridge.externalevents; +package nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks; import java.util.List; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java index e421d9ea2a..bd66c6a8c0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java @@ -102,7 +102,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.User; -import nodomain.freeyourgadget.gadgetbridge.externalevents.OpenTracksController; +import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/workout/WorkoutRequestHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/workout/WorkoutRequestHandler.java index fc69b4e39b..8bb941f062 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/workout/WorkoutRequestHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/workout/WorkoutRequestHandler.java @@ -25,7 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.externalevents.OpenTracksController; +import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController; public class WorkoutRequestHandler { public static void addStateResponse(JSONObject workoutResponse, String type, String msg) throws JSONException { From 236d9d9e2f5091f57e5eb3fd239e8c125330fac0 Mon Sep 17 00:00:00 2001 From: rarder44 Date: Mon, 30 May 2022 13:19:19 +0200 Subject: [PATCH 17/31] Bangle.js - id in http request/response (#2683) added an optional id to identify the request. if a request with id occurs, a response with the same id is returned. Co-authored-by: Rarder44 Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2683 Co-authored-by: rarder44 Co-committed-by: rarder44 --- .../banglejs/BangleJSDeviceSupport.java | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java index 53bdb5bd3d..ad2ed8014f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java @@ -276,10 +276,17 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { } /// Write JSON object of the form {t:taskName, err:message} + private void uartTxJSONError(String taskName, String message) { + uartTxJSONError(taskName,message,null); + } + + private void uartTxJSONError(String taskName, String message,String id) { JSONObject o = new JSONObject(); try { o.put("t", taskName); + if( id!=null) + o.put("id", id); o.put("err", message); } catch (JSONException e) { GB.toast(getContext(), "uartTxJSONError: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); @@ -287,6 +294,8 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { uartTxJSON(taskName, o); } + + private void handleUartRxLine(String line) { LOG.info("UART RX LINE: " + line); @@ -412,9 +421,18 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { } break; case "http": { Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())); + String _id=null; + try { + _id = json.getString("id"); + } catch (JSONException e) { + } + final String id = _id; + + if (BuildConfig.INTERNET_ACCESS && devicePrefs.getBoolean(PREF_DEVICE_INTERNET_ACCESS, false)) { RequestQueue queue = Volley.newRequestQueue(getContext()); String url = json.getString("url"); + String _xmlPath = ""; try { _xmlPath = json.getString("xpath"); @@ -433,12 +451,14 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { XPath xPath = XPathFactory.newInstance().newXPath(); response = xPath.evaluate(xmlPath, inputXML); } catch (Exception error) { - uartTxJSONError("http", error.toString()); + uartTxJSONError("http", error.toString(),id); return; } } try { o.put("t", "http"); + if( id!=null) + o.put("id", id); o.put("resp", response); } catch (JSONException e) { GB.toast(getContext(), "HTTP: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); @@ -449,15 +469,15 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onErrorResponse(VolleyError error) { JSONObject o = new JSONObject(); - uartTxJSONError("http", error.toString()); + uartTxJSONError("http", error.toString(),id); } }); queue.add(stringRequest); } else { if (BuildConfig.INTERNET_ACCESS) - uartTxJSONError("http", "Internet access not enabled, check Gadgetbridge Device Settings"); + uartTxJSONError("http", "Internet access not enabled, check Gadgetbridge Device Settings",id); else - uartTxJSONError("http", "Internet access not enabled in this Gadgetbridge build"); + uartTxJSONError("http", "Internet access not enabled in this Gadgetbridge build",id); } } break; case "intent": { From 8a6c0ddbfe7af6460ad132966e96efec6afdfbe9 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Mon, 30 May 2022 13:28:21 +0200 Subject: [PATCH 18/31] update changelog - bump version --- CHANGELOG.md | 6 +++++- app/build.gradle | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2e9976c14..b999a9767d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ ### Changelog -### NEXT +### 0.67.1 + +* Huami: Fix long music track names not displaying * Amazfit Bip U/Pro/Band 5: enable extended HR/stress monitoring setting +* Pebble: Fix calendar blacklist, view and storage +* FitPro: fix crash, inactivity warning preference to string ### 0.67.0 * Initial Support for Sony WF-1000XM3 diff --git a/app/build.gradle b/app/build.gradle index 76d42bd876..15a94424fd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,8 +55,8 @@ android { multiDexEnabled true // Note: always bump BOTH versionCode and versionName! - versionName "0.67.0" - versionCode 211 + versionName "0.67.1" + versionCode 212 vectorDrawables.useSupportLibrary = true multiDexEnabled true buildConfigField "String", "GIT_HASH_SHORT", "\"${getGitHashShort()}\"" From de4d38ead18ecde64239d20fde2b405d04d2b28e Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Mon, 30 May 2022 20:31:31 +0200 Subject: [PATCH 19/31] update changelogs --- CHANGELOG.md | 5 ++--- app/src/main/res/xml/changelog_master.xml | 6 ++++++ fastlane/metadata/android/en-US/changelogs/212.txt | 4 ++++ 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/212.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index b999a9767d..9d29d9d4bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,10 @@ ### Changelog ### 0.67.1 - * Huami: Fix long music track names not displaying -* Amazfit Bip U/Pro/Band 5: enable extended HR/stress monitoring setting +* Amazfit Bip U/Pro/Band 5: Enable extended HR/stress monitoring setting * Pebble: Fix calendar blacklist, view and storage -* FitPro: fix crash, inactivity warning preference to string +* FitPro: Fix crash, inactivity warning preference to string ### 0.67.0 * Initial Support for Sony WF-1000XM3 diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml index bba56bd899..ecfaeaa347 100644 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -1,5 +1,11 @@ + + Huami: Fix long music track names not displaying + Amazfit Bip U/Pro/Band 5: Enable extended HR/stress monitoring setting + Pebble: Fix calendar blacklist, view and storage + FitPro: Fix crash, inactivity warning preference to string + Initial Support for Sony WF-1000XM3 Initial Support for Galaxy Buds Pro diff --git a/fastlane/metadata/android/en-US/changelogs/212.txt b/fastlane/metadata/android/en-US/changelogs/212.txt new file mode 100644 index 0000000000..7921bd751f --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/212.txt @@ -0,0 +1,4 @@ +* Huami: Fix long music track names not displaying +* Amazfit Bip U/Pro/Band 5: Enable extended HR/stress monitoring setting +* Pebble: Fix calendar blacklist, view and storage +* FitPro: Fix crash, inactivity warning preference to string From d9d906677cf3db6cb753386f2e1d397c3b84feda Mon Sep 17 00:00:00 2001 From: Arjan Schrijver Date: Mon, 30 May 2022 21:52:31 +0200 Subject: [PATCH 20/31] Fossil Hybrid HR: Improve 'Light up on new notification' functionality --- .../assets/fossil_hr/openSourceWatchface.bin | Bin 7814 -> 7956 bytes external/fossil-hr-watchface | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/fossil_hr/openSourceWatchface.bin b/app/src/main/assets/fossil_hr/openSourceWatchface.bin index 41e239c15ac06bb0583ff15c9d11c6151e937d05..6358d3cdfc98afdc90e8f9f699004c3892abd04e 100644 GIT binary patch delta 980 zcmYk4U1%It7>1vj?C#l}-C6e}HAz>SCY5GZ!3ss@BB6ys+tHwjY$;m7wrRVWrkGu6 zmzsuX#h(uTq-u^G^HN?Xq@KfYSxI=0JZDD8bzd^h#Kga96_g>H+KT+}``J7$B3$ zWUJNSEX0REoP($h-gyXL1ThW4H0WsvFF-H@;c19x)X{wRMKKG(GoWX|8w20Yj^_rl zK0-d|+xb%kxucf(e#og6Z`VpY3pID5kh6-G>)U0Sv2w#5_xxZa6)AmN`pDxlKTKz> zW6ig-Vb_50?JW;a$R6LmH|&xRU9~*JP(0Ve-?kV^=0*K$sg1THhY`@@;>~p*Cw={RgQbYPUck89n5)G z42fEBt9{W&5PuB8ClFR3*#Tl4q6P4-s&#kfpxFbXNUnnMG8uwTLDc@8>1Pmp4x$V6 zwg1Z@{sO!$hDptr2dnWSL?sX%sasTrFk=W-*ULXyIb)H`sfEgrv*CIjlI!Z%N_V&Z z63mlu(goCCL8z4@XZo}Tu>`>q#7iI!Lq8yvAy`(6a;P&}hWKkpz5#s$!kg-j+~vnN zA^a{yN=z+-w-tgFNU9Lt56KFIJrK(@Vbor&cb>_Y9KuHX*vZz^RI90ORdK_}W#FLNL>tpr)ryd&{ aJ&@zvQGq&TnuFOC*2nItOgPMv!-pC93TlPy@g&>FY zhg`?sZ6&{YVK@ZAMUa=kz^PtPmvv9RVh6+eXufI(gL*VS>YB;K!=OeW9?>m@cXn`5 z|19`T!4>%1#&|_n|2U-n@dbmrv$)F+hxDsr)8^<}5^4+#tuZo|@w#L(@;WFDu?AIw zcm;?)kT)T^sVnaB_HY#D+S~#IU>s_>Z%KYv!Ac3&6%WDO5jhYO z5KMrW)Sce(YA^}4Nr)#wO@Ziw2B0&(3LGT2`Q|*`6`syi&X9;6y zsta-n#LpqFfLaRS3s8$7@)G6f(9Br5Kch?a8)k;<%XT4$+H-j4?gIk@2lngnWzXv@ Jy7boS$KOjpyr%#F diff --git a/external/fossil-hr-watchface b/external/fossil-hr-watchface index aad2a141cb..f07ed376e9 160000 --- a/external/fossil-hr-watchface +++ b/external/fossil-hr-watchface @@ -1 +1 @@ -Subproject commit aad2a141cb2e151431f8354e52d9b74f6829855a +Subproject commit f07ed376e9046dbcc9c5d7821117c80b2d79ffd1 From 4d0bfb452b37bbad15eb37ab78963d24fa70dfb6 Mon Sep 17 00:00:00 2001 From: Arjan Schrijver Date: Fri, 3 Jun 2022 15:48:48 +0200 Subject: [PATCH 21/31] Fossil Hybrid HR: Allow installation of newer watch apps --- .../gadgetbridge/devices/qhybrid/FossilFileReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilFileReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilFileReader.java index 5e0d74feaa..301df6982b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilFileReader.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FossilFileReader.java @@ -83,7 +83,7 @@ public class FossilFileReader { short handle = buf.getShort(); short version = buf.getShort(); - if ((handle == 5630) && (version == 3)) { + if ((handle == 5630) && (version == 3 || version == 515 || version == 771)) { // This is a watch app or watch face isValid = true; isApp = true; From 621e731d6398b298774b8fc6bd8a87613bb0398f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Fri, 3 Jun 2022 23:07:36 +0100 Subject: [PATCH 22/31] Go to previous PreferenceScreen on back actionbar option Fixes #2692 --- .../devicesettings/DeviceSettingsActivity.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsActivity.java index 9ed08de5d5..0a74095c35 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsActivity.java @@ -17,6 +17,7 @@ package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings; import android.os.Bundle; +import android.view.MenuItem; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceFragmentCompat; @@ -72,4 +73,17 @@ public class DeviceSettingsActivity extends AbstractGBActivity implements .commit(); return true; } -} \ No newline at end of file + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + // Simulate a back press, so that we don't actually exit the activity when + // in a nested PreferenceScreen + this.onBackPressed(); + return true; + } + + return super.onOptionsItemSelected(item); + } +} From c188eccff70d7eac25f626ca2eb186763fd053d9 Mon Sep 17 00:00:00 2001 From: vanous Date: Sat, 4 Jun 2022 11:26:45 +0200 Subject: [PATCH 23/31] Keep main menu items unselected, fix #2351 --- .../activities/ControlCenterv2.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java index 9bd0768c16..5fed54e449 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java @@ -334,30 +334,30 @@ public class ControlCenterv2 extends AppCompatActivity case R.id.action_settings: Intent settingsIntent = new Intent(this, SettingsActivity.class); startActivityForResult(settingsIntent, MENU_REFRESH_CODE); - return true; + return false; //we do not want the drawer menu item to get selected case R.id.action_debug: Intent debugIntent = new Intent(this, DebugActivity.class); startActivity(debugIntent); - return true; + return false; case R.id.action_data_management: Intent dbIntent = new Intent(this, DataManagementActivity.class); startActivity(dbIntent); - return true; + return false; case R.id.action_notification_management: Intent blIntent = new Intent(this, NotificationManagementActivity.class); startActivity(blIntent); - return true; + return false; case R.id.device_action_discover: launchDiscoveryActivity(); - return true; + return false; case R.id.action_quit: GBApplication.quit(); - return true; + return false; case R.id.donation_link: Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("https://liberapay.com/Gadgetbridge")); //TODO: centralize if ever used somewhere else i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(i); - return true; + return false; case R.id.external_changelog: ChangeLog cl = createChangeLog(); try { @@ -365,14 +365,14 @@ public class ControlCenterv2 extends AppCompatActivity } catch (Exception ignored) { GB.toast(getBaseContext(), "Error showing Changelog", Toast.LENGTH_LONG, GB.ERROR); } - return true; + return false; case R.id.about: Intent aboutIntent = new Intent(this, AboutActivity.class); startActivity(aboutIntent); - return true; + return false; } - return true; + return false; } private ChangeLog createChangeLog() { From 5e33e8e58f424164050ed7f33c2c098a9a3948a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Tue, 7 Jun 2022 12:12:21 +0100 Subject: [PATCH 24/31] Mi Band 6: Fix night mode on latest firmware --- .../service/devices/huami/miband3/MiBand3Support.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband3/MiBand3Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband3/MiBand3Support.java index 734acc1cdd..74705d0f90 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband3/MiBand3Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband3/MiBand3Support.java @@ -93,10 +93,10 @@ public class MiBand3Support extends AmazfitBipSupport { switch (nightMode) { case MiBandConst.PREF_NIGHT_MODE_SUNSET: - builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand3Service.COMMAND_NIGHT_MODE_SUNSET); + writeToConfiguration(builder, MiBand3Service.COMMAND_NIGHT_MODE_SUNSET); break; case MiBandConst.PREF_NIGHT_MODE_OFF: - builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand3Service.COMMAND_NIGHT_MODE_OFF); + writeToConfiguration(builder, MiBand3Service.COMMAND_NIGHT_MODE_OFF); break; case MiBandConst.PREF_NIGHT_MODE_SCHEDULED: byte[] cmd = MiBand3Service.COMMAND_NIGHT_MODE_SCHEDULED.clone(); @@ -113,7 +113,7 @@ public class MiBand3Support extends AmazfitBipSupport { cmd[4] = (byte) calendar.get(Calendar.HOUR_OF_DAY); cmd[5] = (byte) calendar.get(Calendar.MINUTE); - builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), cmd); + writeToConfiguration(builder, cmd); break; default: LOG.error("Invalid night mode: " + nightMode); From d6658b41b548aadd8d56ed669051cef12eab63f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Mon, 6 Jun 2022 22:27:04 +0100 Subject: [PATCH 25/31] Huami: Fix heart rate measurement interval on connection --- .../gadgetbridge/devices/huami/HuamiCoordinator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java index 42575cf63e..a671f1cfbf 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java @@ -270,7 +270,7 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator { public static int getHeartRateMeasurementInterval(String deviceAddress) { Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress)); - return GBApplication.getPrefs().getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_MEASUREMENT_INTERVAL, 0) / 60; + return prefs.getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_MEASUREMENT_INTERVAL, 0) / 60; } public static boolean getHeartrateActivityMonitoring(String deviceAddress) throws IllegalArgumentException { From 91fa713bea049eb545b98479aefbfb187e9ba762 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Wed, 8 Jun 2022 14:31:28 +0200 Subject: [PATCH 26/31] Mi Band 6: add sleep menu item (also to shortcuts) --- app/src/main/res/values/arrays.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index f9d8d13772..325e74cbdb 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -462,6 +462,7 @@ @string/menuitem_activity @string/menuitem_eventreminder @string/menuitem_pai + @string/menuitem_sleep @string/menuitem_worldclock @string/menuitem_stress @string/menuitem_cycles @@ -489,6 +490,7 @@ @string/p_menuitem_activity @string/p_menuitem_eventreminder @string/p_menuitem_pai + @string/p_menuitem_sleep @string/p_menuitem_worldclock @string/p_menuitem_stress @string/p_menuitem_cycles @@ -516,6 +518,7 @@ @string/p_menuitem_activity @string/p_menuitem_eventreminder @string/p_menuitem_pai + @string/p_menuitem_sleep @string/p_menuitem_worldclock @string/p_menuitem_stress @string/p_menuitem_cycles @@ -540,6 +543,7 @@ @string/menuitem_mutephone @string/menuitem_eventreminder @string/menuitem_pai + @string/menuitem_sleep @string/menuitem_worldclock @string/menuitem_stress @string/menuitem_cycles @@ -565,6 +569,7 @@ @string/p_menuitem_mutephone @string/p_menuitem_eventreminder @string/p_menuitem_pai + @string/p_menuitem_sleep @string/p_menuitem_worldclock @string/p_menuitem_stress @string/p_menuitem_cycles From ee93cce16d8dc499d465c0e88f66feb1849ff66c Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Wed, 8 Jun 2022 14:36:14 +0200 Subject: [PATCH 27/31] Mi Band 6: Whitelist FW 1.9.6.16 --- .../service/devices/huami/miband6/MiBand6FirmwareInfo.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband6/MiBand6FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband6/MiBand6FirmwareInfo.java index e4d26c65df..ec47bacea2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband6/MiBand6FirmwareInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband6/MiBand6FirmwareInfo.java @@ -39,11 +39,12 @@ public class MiBand6FirmwareInfo extends HuamiFirmwareInfo { // firmware crcToVersion.put(47447, "1.0.1.36"); crcToVersion.put(41380, "1.0.4.38"); - crcToVersion.put(8209, "1.0.6.10"); + crcToVersion.put(8209, "1.0.6.10-16"); // resources crcToVersion.put(54803, "1.0.1.36"); crcToVersion.put(14596, "1.0.4.38"); crcToVersion.put(63397, "1.0.6.10"); + crcToVersion.put(19391, "1.0.6.16"); } public MiBand6FirmwareInfo(byte[] bytes) { From b07cd54468b61edc7e14df2ec3edea32d8ba8a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sat, 4 Jun 2022 21:20:28 +0100 Subject: [PATCH 28/31] Mi Band 5: Send GPS location to band during workout --- .../activities/DebugActivity.java | 9 + .../DeviceSettingsPreferenceConst.java | 2 + .../gadgetbridge/devices/EventHandler.java | 3 + .../devices/huami/HuamiCoordinator.java | 6 + .../devices/huami/HuamiService.java | 7 +- .../huami/miband5/MiBand5Coordinator.java | 1 + .../gps/AbstractLocationProvider.java | 49 +++++ .../gps/GBLocationListener.java | 80 ++++++++ .../externalevents/gps/GBLocationManager.java | 86 +++++++++ .../gps/MockLocationProvider.java | 96 ++++++++++ .../gps/PhoneGpsLocationProvider.java | 66 +++++++ .../gadgetbridge/impl/GBDeviceService.java | 8 + .../gadgetbridge/model/DeviceService.java | 2 + .../service/DeviceCommunicationService.java | 104 +--------- .../service/ServiceDeviceSupport.java | 9 + .../btle/AbstractBTLEDeviceSupport.java | 6 + .../devices/huami/HuamiDeviceEvent.java | 1 + .../devices/huami/HuamiPhoneGpsStatus.java | 47 +++++ .../service/devices/huami/HuamiSupport.java | 177 +++++++++++++++++- ...va => HuamiWorkoutScreenActivityType.java} | 13 +- .../devices/huami/HuamiWorkoutStatus.java | 45 +++++ .../huami/HuamiWorkoutTrackActivityType.java | 56 ++++++ .../serial/AbstractSerialDeviceSupport.java | 8 + .../service/serial/GBDeviceProtocol.java | 6 + .../freeyourgadget/gadgetbridge/util/GB.java | 29 +++ app/src/main/res/drawable/ic_gps_location.xml | 5 + app/src/main/res/layout/activity_debug.xml | 8 + app/src/main/res/values/strings.xml | 6 + ...evicesettings_workout_send_gps_to_band.xml | 9 + 29 files changed, 830 insertions(+), 114 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/AbstractLocationProvider.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationListener.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationManager.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/MockLocationProvider.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/PhoneGpsLocationProvider.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiPhoneGpsStatus.java rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/{HuamiWorkoutActivityType.java => HuamiWorkoutScreenActivityType.java} (75%) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutStatus.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutTrackActivityType.java create mode 100644 app/src/main/res/drawable/ic_gps_location.xml create mode 100644 app/src/main/res/xml/devicesettings_workout_send_gps_to_band.xml 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 7c3c994690..d34ec77600 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java @@ -84,6 +84,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; +import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager; import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksContentObserver; import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -527,6 +528,14 @@ public class DebugActivity extends AbstractGBActivity { } }); + Button stopPhoneGpsLocationListener = findViewById(R.id.stopPhoneGpsLocationListener); + stopPhoneGpsLocationListener.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + GBLocationManager.stopAll(getBaseContext()); + } + }); + Button showStatusFitnessAppTracking = findViewById(R.id.showStatusFitnessAppTracking); final int delay = 2 * 1000; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index 36f6638a91..01dace564b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -116,6 +116,8 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_DO_NOT_DISTURB_AUTOMATIC = "automatic"; public static final String PREF_DO_NOT_DISTURB_SCHEDULED = "scheduled"; + public static final String PREF_WORKOUT_SEND_GPS_TO_BAND = "workout_send_gps_to_band"; + public static final String PREF_FIND_PHONE = "prefs_find_phone"; public static final String PREF_FIND_PHONE_DURATION = "prefs_find_phone_duration"; public static final String PREF_AUTOLIGHT = "autolight"; 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 a0ec99c1c0..f8674afd81 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java @@ -18,6 +18,7 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices; +import android.location.Location; import android.net.Uri; import java.util.ArrayList; @@ -130,4 +131,6 @@ public interface EventHandler { void onSetLedColor(int color); void onPowerOff(); + + void onSetGpsLocation(Location location); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java index a671f1cfbf..f6adb031e0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java @@ -363,6 +363,12 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator { return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_LIFT_WRIST, false); } + public static boolean getWorkoutSendGpsToBand(String deviceAddress) { + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress); + + return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_SEND_GPS_TO_BAND, false); + } + @Override public boolean supportsScreenshots() { return false; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiService.java index d2a07bb410..9742aee5b5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiService.java @@ -35,8 +35,9 @@ public class HuamiService { public static final UUID UUID_CHARACTERISTIC_FIRMWARE_DATA = UUID.fromString("00001532-0000-3512-2118-0009af100700"); public static final UUID UUID_UNKNOWN_CHARACTERISTIC0 = UUID.fromString("00000000-0000-3512-2118-0009af100700"); - public static final UUID UUID_UNKNOWN_CHARACTERISTIC1 = UUID.fromString("00000001-0000-3512-2118-0009af100700"); - public static final UUID UUID_UNKNOWN_CHARACTERISTIC2 = UUID.fromString("00000002-0000-3512-2118-0009af100700"); + public static final UUID UUID_UNKNOWN_RAW_SENSOR_CONTROL = UUID.fromString("00000001-0000-3512-2118-0009af100700"); + public static final UUID UUID_UNKNOWN_RAW_SENSOR_DATA = UUID.fromString("00000002-0000-3512-2118-0009af100700"); + /** * Alarms, Display and other configuration. */ @@ -48,9 +49,11 @@ public class HuamiService { public static final UUID UUID_CHARACTERISTIC_8_USER_SETTINGS = UUID.fromString("00000008-0000-3512-2118-0009af100700"); // service uuid fee1 public static final UUID UUID_CHARACTERISTIC_AUTH = UUID.fromString("00000009-0000-3512-2118-0009af100700"); + public static final UUID UUID_CHARACTERISTIC_WORKOUT = UUID.fromString("0000000f-0000-3512-2118-0009af100700"); public static final UUID UUID_CHARACTERISTIC_DEVICEEVENT = UUID.fromString("00000010-0000-3512-2118-0009af100700"); public static final UUID UUID_CHARACTERISTIC_AUDIO = UUID.fromString("00000012-0000-3512-2118-0009af100700"); public static final UUID UUID_CHARACTERISTIC_AUDIODATA = UUID.fromString("00000013-0000-3512-2118-0009af100700"); + public static final UUID UUID_UNKNOWN_CHARACTERISTIC5 = UUID.fromString("00000014-0000-3512-2118-0009af100700"); public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE = UUID.fromString("00000016-0000-3512-2118-0009af100700"); public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ = UUID.fromString("00000017-0000-3512-2118-0009af100700"); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5Coordinator.java index 0c40d3489a..00e5c8b4b0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband5/MiBand5Coordinator.java @@ -115,6 +115,7 @@ public class MiBand5Coordinator extends HuamiCoordinator { R.xml.devicesettings_nightmode, R.xml.devicesettings_liftwrist_display_sensitivity, R.xml.devicesettings_inactivity_dnd, + R.xml.devicesettings_workout_send_gps_to_band, R.xml.devicesettings_swipeunlock, R.xml.devicesettings_sync_calendar, R.xml.devicesettings_reserve_reminders_calendar, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/AbstractLocationProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/AbstractLocationProvider.java new file mode 100644 index 0000000000..a361dfca0d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/AbstractLocationProvider.java @@ -0,0 +1,49 @@ +/* Copyright (C) 2022 José Rebelo + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.externalevents.gps; + +import android.content.Context; +import android.location.LocationListener; + +/** + * An abstract location provider, which periodically sends a location update to the provided {@link LocationListener}. + */ +public abstract class AbstractLocationProvider { + private final LocationListener locationListener; + + public AbstractLocationProvider(final LocationListener locationListener) { + this.locationListener = locationListener; + } + + protected final LocationListener getLocationListener() { + return this.locationListener; + } + + /** + * Start sending periodic location updates. + * + * @param context the {@link Context}. + */ + abstract void start(final Context context); + + /** + * Stop sending periodic location updates. + * + * @param context the {@link Context}. + */ + abstract void stop(final Context context); +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationListener.java new file mode 100644 index 0000000000..edd9c61ab7 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationListener.java @@ -0,0 +1,80 @@ +/* Copyright (C) 2022 José Rebelo + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.externalevents.gps; + +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler; +import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +/** + * An implementation of a {@link LocationListener} that forwards the location updates to the + * provided {@link EventHandler}. + */ +public class GBLocationListener implements LocationListener { + private static final Logger LOG = LoggerFactory.getLogger(GBLocationListener.class); + + private final EventHandler eventHandler; + + private Location previousLocation; + + public GBLocationListener(final EventHandler eventHandler) { + this.eventHandler = eventHandler; + } + + @Override + public void onLocationChanged(final Location location) { + LOG.info("Location changed: {}", location); + + // The location usually doesn't contain speed, compute it from the previous location + if (previousLocation != null && !location.hasSpeed()) { + long timeInterval = (location.getTime() - previousLocation.getTime()) / 1000L; + float distanceInMeters = previousLocation.distanceTo(location); + location.setSpeed(distanceInMeters / timeInterval); + } + + previousLocation = location; + + eventHandler.onSetGpsLocation(location); + } + + @Override + public void onProviderDisabled(final String provider) { + LOG.info("onProviderDisabled: {}", provider); + } + + @Override + public void onProviderEnabled(final String provider) { + LOG.info("onProviderDisabled: {}", provider); + } + + @Override + public void onStatusChanged(final String provider, final int status, final Bundle extras) { + LOG.info("onStatusChanged: {}", provider, status); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationManager.java new file mode 100644 index 0000000000..c657c0017e --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/GBLocationManager.java @@ -0,0 +1,86 @@ +/* Copyright (C) 2022 José Rebelo + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.externalevents.gps; + +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.os.Looper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +/** + * A static location manager, which keeps track of what providers are currently running. A notification is kept + * while there is at least one provider runnin. + */ +public class GBLocationManager { + private static final Logger LOG = LoggerFactory.getLogger(GBLocationManager.class); + + /** + * The current number of running listeners. + */ + private static Map providers = new HashMap<>(); + + public static void start(final Context context, final EventHandler eventHandler) { + if (providers.containsKey(eventHandler)) { + LOG.warn("EventHandler already registered"); + return; + } + + GB.createGpsNotification(context, providers.size() + 1); + + final GBLocationListener locationListener = new GBLocationListener(eventHandler); + final AbstractLocationProvider locationProvider = new PhoneGpsLocationProvider(locationListener); + + locationProvider.start(context); + + providers.put(eventHandler, locationProvider); + } + + public static void stop(final Context context, final EventHandler eventHandler) { + final AbstractLocationProvider locationProvider = providers.remove(eventHandler); + + if (locationProvider != null) { + LOG.warn("EventHandler not registered"); + + locationProvider.stop(context); + } + + if (!providers.isEmpty()) { + GB.createGpsNotification(context, providers.size()); + } else { + GB.removeGpsNotification(context); + } + } + + public static void stopAll(final Context context) { + for (EventHandler eventHandler : providers.keySet()) { + stop(context, eventHandler); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/MockLocationProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/MockLocationProvider.java new file mode 100644 index 0000000000..60f3f75bf6 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/MockLocationProvider.java @@ -0,0 +1,96 @@ +/* Copyright (C) 2022 José Rebelo + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.externalevents.gps; + +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.os.Handler; +import android.os.Looper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition; + +/** + * A mock location provider which keeps updating the location at a constant speed, starting from the + * last known location. Useful for local tests. + */ +public class MockLocationProvider extends AbstractLocationProvider { + private static final Logger LOG = LoggerFactory.getLogger(MockLocationProvider.class); + + private Location previousLocation = new CurrentPosition().getLastKnownLocation(); + + /** + * Interval between location updates, in milliseconds. + */ + private final int interval = 1000; + + /** + * Difference between location updates, in degrees. + */ + private final float coordDiff = 0.0002f; + + /** + * Whether the handler is running. + */ + private boolean running = false; + + private final Handler handler = new Handler(Looper.getMainLooper()); + + private final Runnable locationUpdateRunnable = new Runnable() { + @Override + public void run() { + if (!running) { + return; + } + + final Location newLocation = new Location(previousLocation); + newLocation.setLatitude(previousLocation.getLatitude() + coordDiff); + newLocation.setTime(System.currentTimeMillis()); + + getLocationListener().onLocationChanged(newLocation); + + previousLocation = newLocation; + + if (running) { + handler.postDelayed(this, interval); + } + } + }; + + public MockLocationProvider(LocationListener locationListener) { + super(locationListener); + } + + @Override + void start(final Context context) { + LOG.info("Starting mock location provider"); + + running = true; + handler.postDelayed(locationUpdateRunnable, interval); + } + + @Override + void stop(final Context context) { + LOG.info("Stopping mock location provider"); + + running = false; + handler.removeCallbacksAndMessages(null); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/PhoneGpsLocationProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/PhoneGpsLocationProvider.java new file mode 100644 index 0000000000..88880a4eb0 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/gps/PhoneGpsLocationProvider.java @@ -0,0 +1,66 @@ +/* Copyright (C) 2022 José Rebelo + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.externalevents.gps; + +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Looper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A location provider that uses the phone GPS, using {@link LocationManager}. + */ +public class PhoneGpsLocationProvider extends AbstractLocationProvider { + private static final Logger LOG = LoggerFactory.getLogger(PhoneGpsLocationProvider.class); + + private static final int INTERVAL_MIN_TIME = 1000; + private static final int INTERVAL_MIN_DISTANCE = 0; + + public PhoneGpsLocationProvider(LocationListener locationListener) { + super(locationListener); + } + + @Override + void start(final Context context) { + LOG.info("Starting phone gps location provider"); + + final LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + locationManager.removeUpdates(getLocationListener()); + locationManager.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + INTERVAL_MIN_TIME, + INTERVAL_MIN_DISTANCE, + getLocationListener(), + Looper.getMainLooper() + ); + + final Location lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); + LOG.debug("Last known location: {}", lastKnownLocation); + } + + @Override + void stop(final Context context) { + LOG.info("Stopping phone gps location provider"); + + final LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + locationManager.removeUpdates(getLocationListener()); + } +} 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 d0f12cc736..ff04bde7c5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -23,6 +23,7 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.database.Cursor; +import android.location.Location; import android.net.Uri; import android.os.Build; import android.provider.ContactsContract; @@ -471,4 +472,11 @@ public class GBDeviceService implements DeviceService { Intent intent = createIntent().setAction(ACTION_POWER_OFF); invokeService(intent); } + + @Override + public void onSetGpsLocation(Location location) { + Intent intent = createIntent().setAction(ACTION_SET_GPS_LOCATION); + intent.putExtra(EXTRA_GPS_LOCATION, location); + invokeService(intent); + } } 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 bd22e9af38..c653dae774 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -70,6 +70,7 @@ public interface DeviceService extends EventHandler { String ACTION_SEND_WEATHER = PREFIX + ".action.send_weather"; String ACTION_TEST_NEW_FUNCTION = PREFIX + ".action.test_new_function"; String ACTION_SET_FM_FREQUENCY = PREFIX + ".action.set_fm_frequency"; + String ACTION_SET_GPS_LOCATION = PREFIX + ".action.set_gps_location"; String ACTION_SET_LED_COLOR = PREFIX + ".action.set_led_color"; String ACTION_POWER_OFF = PREFIX + ".action.power_off"; String EXTRA_NOTIFICATION_BODY = "notification_body"; @@ -122,6 +123,7 @@ public interface DeviceService extends EventHandler { String EXTRA_RECORDED_DATA_TYPES = "data_types"; String EXTRA_FM_FREQUENCY = "fm_frequency"; String EXTRA_LED_COLOR = "led_color"; + String EXTRA_GPS_LOCATION = "gps_location"; String EXTRA_RESET_FLAGS = "reset_flags"; /** 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 d83db85c6c..ce8eed497d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -30,6 +30,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.location.Location; import android.net.Uri; import android.os.Handler; import android.os.IBinder; @@ -86,104 +87,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.LanguageUtils; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TRANSLITERATION_ENABLED; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ADD_CALENDAREVENT; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_CONFIGURE; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_REORDER; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CALLSTATE; -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_DELETE_CALENDAREVENT; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETE_NOTIFICATION; -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_HEARTRATE_MEASUREMENT; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_STEPS; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FETCH_RECORDED_DATA; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FIND_DEVICE; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_HEARTRATE_TEST; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_INSTALL; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_POWER_OFF; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_READ_CONFIGURATION; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_APPINFO; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_DEVICEINFO; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_SCREENSHOT; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_RESET; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SEND_CONFIGURATION; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SEND_WEATHER; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETCANNEDMESSAGES; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICINFO; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICSTATE; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETTIME; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_ALARMS; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_CONSTANT_VIBRATION; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_FM_FREQUENCY; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_HEARTRATE_MEASUREMENT_INTERVAL; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_LED_COLOR; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_PHONE_VOLUME; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_REMINDERS; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_WORLD_CLOCKS; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_TEST_NEW_FUNCTION; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ALARMS; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_CONFIG; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_CONFIG_ID; -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_CALENDAREVENT_DESCRIPTION; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_DURATION; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_ID; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_LOCATION; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TIMESTAMP; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TITLE; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TYPE; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_COMMAND; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_DISPLAYNAME; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_DNDSUPPRESSED; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_PHONENUMBER; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CANNEDMESSAGES; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CANNEDMESSAGES_TYPE; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONFIG; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONNECT_FIRST_TIME; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FM_FREQUENCY; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_INTERVAL_SECONDS; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_LED_COLOR; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_DURATION; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_POSITION; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_RATE; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_REPEAT; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_SHUFFLE; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_STATE; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACK; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACKCOUNT; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACKNR; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_ACTIONS; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_BODY; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_DNDSUPPRESSED; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_FLAGS; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_ICONID; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_ID; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_PEBBLE_COLOR; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_PHONENUMBER; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SENDER; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SOURCEAPPID; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SOURCENAME; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SUBJECT; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TITLE; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TYPE; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_PHONE_VOLUME; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_RECORDED_DATA_TYPES; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_REMINDERS; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_RESET_FLAGS; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_VIBRATION_INTENSITY; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WORLD_CLOCKS; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.*; public class DeviceCommunicationService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener { private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class); @@ -657,6 +561,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere mDeviceSupport.onSetFmFrequency(frequency); } break; + case ACTION_SET_GPS_LOCATION: + final Location location = intent.getParcelableExtra(EXTRA_GPS_LOCATION); + mDeviceSupport.onSetGpsLocation(location); + break; } } 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 37224e8873..fe6fffd30d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java @@ -20,6 +20,7 @@ package nodomain.freeyourgadget.gadgetbridge.service; import android.bluetooth.BluetoothAdapter; import android.content.Context; +import android.location.Location; import android.net.Uri; import org.slf4j.Logger; @@ -438,4 +439,12 @@ public class ServiceDeviceSupport implements DeviceSupport { } delegate.onPowerOff(); } + + @Override + public void onSetGpsLocation(Location location) { + if (checkBusy("set gps location")) { + return; + } + delegate.onSetGpsLocation(location); + } } 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 26c1a587cc..28cabe7c4e 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 @@ -23,6 +23,7 @@ import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.content.Intent; +import android.location.Location; import org.slf4j.Logger; @@ -379,6 +380,11 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im } + @Override + public void onSetGpsLocation(Location location) { + + } + @Override public void onSetReminders(ArrayList reminders) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiDeviceEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiDeviceEvent.java index 5ac91e11f3..c299eb8454 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiDeviceEvent.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiDeviceEvent.java @@ -31,6 +31,7 @@ public class HuamiDeviceEvent { public static final byte TICK_30MIN = 0x0e; // unsure public static final byte FIND_PHONE_STOP = 0x0f; public static final byte MTU_REQUEST = 0x16; + public static final byte WORKOUT_STARTING = 0x14; public static final byte ALARM_CHANGED = 0x1a; public static final byte MUSIC_CONTROL = (byte) 0xfe; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiPhoneGpsStatus.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiPhoneGpsStatus.java new file mode 100644 index 0000000000..c855f12d7d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiPhoneGpsStatus.java @@ -0,0 +1,47 @@ +/* Copyright (C) 2022 José Rebelo + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huami; + +/** + * The phone GPS status, to signal the band. + */ +public enum HuamiPhoneGpsStatus { + ACQUIRED(0x01), + SEARCHING(0x02), + DISABLED(0x04), + ; + + private final byte code; + + HuamiPhoneGpsStatus(final int code) { + this.code = (byte) code; + } + + public byte getCode() { + return code; + } + + public static HuamiPhoneGpsStatus fromCode(final byte code) { + for (final HuamiPhoneGpsStatus type : values()) { + if (type.getCode() == code) { + return type; + } + } + + return null; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java index bd66c6a8c0..2ad05be793 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java @@ -26,6 +26,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.location.Location; import android.media.AudioManager; import android.net.Uri; import android.widget.Toast; @@ -102,6 +103,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.User; +import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager; import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; @@ -458,6 +460,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport { builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIO), enable); builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIODATA), enable); builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT), enable); + builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_WORKOUT), enable); if (characteristicChunked2021Read != null) { builder.notify(characteristicChunked2021Read, enable); } @@ -1848,19 +1851,169 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport { requestMTU(mtu); } */ + break; + case HuamiDeviceEvent.WORKOUT_STARTING: + final HuamiWorkoutTrackActivityType activityType = HuamiWorkoutTrackActivityType.fromCode(value[3]); + this.workoutNeedsGps = (value[2] == 1); + + if (activityType == null) { + LOG.warn("Unknown workout activity type {}", String.format("0x%x", value[3])); + } + + LOG.info("Workout starting on band: {}, needs gps = {}", activityType, workoutNeedsGps); + + final boolean sendGpsToBand = HuamiCoordinator.getWorkoutSendGpsToBand(getDevice().getAddress()); + + if (workoutNeedsGps) { + if (sendGpsToBand) { + lastPhoneGpsSent = 0; + sendPhoneGpsStatus(HuamiPhoneGpsStatus.SEARCHING); + GBLocationManager.start(getContext(), this); + } else { + sendPhoneGpsStatus(HuamiPhoneGpsStatus.DISABLED); + } + } + break; default: - LOG.warn("unhandled event " + value[0]); + LOG.warn("unhandled event {}", String.format("0x%x", value[0])); } } - private void requestMTU(int mtu) { - if (GBApplication.isRunningLollipopOrLater()) { - new TransactionBuilder("requestMtu") - .requestMtu(mtu) - .queue(getQueue()); - mMTU = mtu; + /** + * Track whether the currently selected workout needs gps (received in {@link #handleDeviceEvent}, so we can start the activity tracking + * if needed in {@link #handleDeviceWorkoutEvent}, since in there we don't know what's the current workout. + */ + private boolean workoutNeedsGps = false; + + /** + * Track the last time we actually sent a gps location. We need to signal that GPS as re-acquired if the last update was too long ago. + */ + private long lastPhoneGpsSent = 0; + + private void handleDeviceWorkoutEvent(byte[] value) { + if (value == null || value.length == 0) { + return; } + + switch (value[0]) { + case 0x11: + final HuamiWorkoutStatus status = HuamiWorkoutStatus.fromCode(value[1]); + if (status == null) { + LOG.warn("Unknown workout status {}", String.format("0x%x", value[1])); + return; + } + + LOG.info("Got workout status {}", status); + + final boolean sendGpsToBand = HuamiCoordinator.getWorkoutSendGpsToBand(getDevice().getAddress()); + + switch (status) { + case Start: + break; + case End: + GBLocationManager.stop(getContext(), this); + + break; + } + + break; + default: + LOG.warn("Unhandled workout event {}", String.format("0x%x", value[0])); + } + } + + @Override + public void onSetGpsLocation(final Location location) { + if (characteristicChunked == null || location == null) { + return; + } + + final boolean sendGpsToBand = HuamiCoordinator.getWorkoutSendGpsToBand(getDevice().getAddress()); + + if (!sendGpsToBand) { + LOG.warn("Sending GPS to band is disabled, ignoring location update"); + return; + } + + int flags = 0x40000; + int length = 1 + 4 + 31; + + boolean newGpsLock = System.currentTimeMillis() - lastPhoneGpsSent > 5000; + lastPhoneGpsSent = System.currentTimeMillis(); + + if (newGpsLock) { + flags |= 0x01; + length += 1; + } + + final ByteBuffer buf = ByteBuffer.allocate(length); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.put((byte) 0x06); + buf.putInt(flags); + + if (newGpsLock) { + buf.put((byte) 0x01); + } + + buf.putInt((int) (location.getLongitude() * 3000000.0)); + buf.putInt((int) (location.getLatitude() * 3000000.0)); + buf.putInt((int) location.getSpeed() * 10); + + buf.putInt((int) (location.getAltitude() * 100)); + buf.putLong(location.getTime()); + + // Seems to always be ff ? + buf.putInt(0xffffffff); + + // Not sure what this is, maybe bearing? It changes while moving, but + // doesn't seem to be needed on the Mi Band 5 + buf.putShort((short) 0x00); + + // Seems to always be 0 ? + buf.put((byte) 0x00); + + try { + final TransactionBuilder builder = performInitialized("send phone gps location"); + writeToChunked(builder, 6, buf.array()); + builder.queue(getQueue()); + } catch (final IOException e) { + LOG.error("Unable to send location", e); + } + + LOG.info("sendLocationToBand: {}", location); + } + + private void sendPhoneGpsStatus(final HuamiPhoneGpsStatus status) { + int flags = 0x01; + final ByteBuffer buf = ByteBuffer.allocate(1 + 4 + 1); + + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.put((byte) 0x06); + buf.putInt(flags); + + buf.put(status.getCode()); + + try { + final TransactionBuilder builder = performInitialized("send phone gps status"); + writeToChunked(builder, 6, buf.array()); + builder.queue(getQueue()); + } catch (final IOException e) { + LOG.error("Unable to send location", e); + } + + LOG.info("sendPhoneGpsStatus: {}", status); + } + + private void requestMTU(int mtu) { + if (!GBApplication.isRunningLollipopOrLater()) { + LOG.warn("Requesting MTU is only supported in Lollipop or later"); + return; + } + new TransactionBuilder("requestMtu") + .requestMtu(mtu) + .queue(getQueue()); + mMTU = mtu; } private void acknowledgeFindPhone() { @@ -1985,6 +2138,9 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport { } else if (HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT.equals(characteristicUUID)) { handleDeviceEvent(characteristic.getValue()); return true; + } else if (HuamiService.UUID_CHARACTERISTIC_WORKOUT.equals(characteristicUUID)) { + handleDeviceWorkoutEvent(characteristic.getValue()); + return true; } else if (HuamiService.UUID_CHARACTERISTIC_7_REALTIME_STEPS.equals(characteristicUUID)) { handleRealtimeSteps(characteristic.getValue()); return true; @@ -2026,6 +2182,9 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport { } else if (HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT.equals(characteristicUUID)) { handleDeviceEvent(characteristic.getValue()); return true; + } else if (HuamiService.UUID_CHARACTERISTIC_WORKOUT.equals(characteristicUUID)) { + handleDeviceWorkoutEvent(characteristic.getValue()); + return true; } else { LOG.info("Unhandled characteristic read: " + characteristicUUID); logMessageContent(characteristic.getValue()); @@ -3204,7 +3363,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport { int pos = 2; for (final String workoutType : enabledActivityTypes) { - command[pos++] = HuamiWorkoutActivityType.fromPrefValue(workoutType).getCode(); + command[pos++] = HuamiWorkoutScreenActivityType.fromPrefValue(workoutType).getCode(); command[pos++] = 0x00; command[pos++] = 0x01; } @@ -3212,7 +3371,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport { // Send all the remaining disabled workout types for (final String workoutType : allActivityTypes) { if (!enabledActivityTypes.contains(workoutType)) { - command[pos++] = HuamiWorkoutActivityType.fromPrefValue(workoutType).getCode(); + command[pos++] = HuamiWorkoutScreenActivityType.fromPrefValue(workoutType).getCode(); command[pos++] = 0x00; command[pos++] = 0x00; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutActivityType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutScreenActivityType.java similarity index 75% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutActivityType.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutScreenActivityType.java index 70688310c4..a5a5906c21 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutActivityType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutScreenActivityType.java @@ -18,7 +18,10 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami; import java.util.Locale; -public enum HuamiWorkoutActivityType { +/** + * The workout types, to configure the workouts screen on the band. + */ +public enum HuamiWorkoutScreenActivityType { OutdoorRunning(0x01), Walking(0x06), Treadmill(0x08), @@ -33,7 +36,7 @@ public enum HuamiWorkoutActivityType { private final byte code; - HuamiWorkoutActivityType(final int code) { + HuamiWorkoutScreenActivityType(final int code) { this.code = (byte) code; } @@ -41,12 +44,12 @@ public enum HuamiWorkoutActivityType { return code; } - public static HuamiWorkoutActivityType fromPrefValue(final String prefValue) { - for (HuamiWorkoutActivityType type : values()) { + public static HuamiWorkoutScreenActivityType fromPrefValue(final String prefValue) { + for (final HuamiWorkoutScreenActivityType type : values()) { if (type.name().toLowerCase(Locale.ROOT).equals(prefValue.replace("_", "").toLowerCase(Locale.ROOT))) { return type; } } - throw new RuntimeException("No matching HuamiWorkoutActivityType for pref value: " + prefValue); + throw new RuntimeException("No matching HuamiWorkoutScreenActivityType for pref value: " + prefValue); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutStatus.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutStatus.java new file mode 100644 index 0000000000..651fb320a9 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutStatus.java @@ -0,0 +1,45 @@ +/* Copyright (C) 2022 José Rebelo + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huami; + +public enum HuamiWorkoutStatus { + Start(0x02), + Pause(0x03), + Resume(0x04), + End(0x05), + ; + + private final byte code; + + HuamiWorkoutStatus(final int code) { + this.code = (byte) code; + } + + public byte getCode() { + return code; + } + + public static HuamiWorkoutStatus fromCode(final byte code) { + for (final HuamiWorkoutStatus type : values()) { + if (type.getCode() == code) { + return type; + } + } + + return null; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutTrackActivityType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutTrackActivityType.java new file mode 100644 index 0000000000..0757d70c64 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiWorkoutTrackActivityType.java @@ -0,0 +1,56 @@ +/* Copyright (C) 2022 José Rebelo + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huami; + +import java.util.Locale; + +/** + * The workout types, used to start / when workout tracking starts on the band. + */ +public enum HuamiWorkoutTrackActivityType { + OutdoorRunning(0x01), + Walking(0x04), + Treadmill(0x02), + OutdoorCycling(0x03), + IndoorCycling(0x09), + Elliptical(0x06), + PoolSwimming(0x05), + Freestyle(0x0b), + JumpRope(0x08), + RowingMachine(0x07), + Yoga(0x0a); + + private final byte code; + + HuamiWorkoutTrackActivityType(final int code) { + this.code = (byte) code; + } + + public byte getCode() { + return code; + } + + public static HuamiWorkoutTrackActivityType fromCode(final byte code) { + for (final HuamiWorkoutTrackActivityType type : values()) { + if (type.getCode() == code) { + return type; + } + } + + return null; + } +} 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 245f5c5d7b..5e5150b3e1 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 @@ -17,6 +17,8 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.serial; +import android.location.Location; + import java.util.ArrayList; import java.util.UUID; @@ -289,4 +291,10 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport byte[] bytes = gbDeviceProtocol.encodeWorldClocks(clocks); sendToDevice(bytes); } + + @Override + public void onSetGpsLocation(Location location) { + byte[] bytes = gbDeviceProtocol.encodeGpsLocation(location); + 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 e2f9005da5..580d562928 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 @@ -17,6 +17,8 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.serial; +import android.location.Location; + import java.util.ArrayList; import java.util.UUID; @@ -163,4 +165,8 @@ public abstract class GBDeviceProtocol { public byte[] encodeFmFrequency(float frequency) { return null; } + + public byte[] encodeGpsLocation(Location location) { + 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 d2cbd09bb3..53b7c1c51d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java @@ -65,6 +65,7 @@ public class GB { public static final String NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID = "gadgetbridge_high_priority"; public static final String NOTIFICATION_CHANNEL_ID_TRANSFER = "gadgetbridge transfer"; public static final String NOTIFICATION_CHANNEL_ID_LOW_BATTERY = "low_battery"; + public static final String NOTIFICATION_CHANNEL_ID_GPS = "gps"; public static final int NOTIFICATION_ID = 1; public static final int NOTIFICATION_ID_INSTALL = 2; @@ -72,6 +73,7 @@ public class GB { public static final int NOTIFICATION_ID_TRANSFER = 4; public static final int NOTIFICATION_ID_EXPORT_FAILED = 5; public static final int NOTIFICATION_ID_PHONE_FIND = 6; + public static final int NOTIFICATION_ID_GPS = 7; public static final int NOTIFICATION_ID_ERROR = 42; private static final Logger LOG = LoggerFactory.getLogger(GB.class); @@ -122,6 +124,12 @@ public class GB { context.getString(R.string.notification_channel_low_battery_name), NotificationManager.IMPORTANCE_DEFAULT); notificationManager.createNotificationChannel(channelLowBattery); + + NotificationChannel channelGps = new NotificationChannel( + NOTIFICATION_CHANNEL_ID_GPS, + context.getString(R.string.notification_channel_gps), + NotificationManager.IMPORTANCE_MIN); + notificationManager.createNotificationChannel(channelGps); } notificationChannelsCreated = true; @@ -440,6 +448,27 @@ public class GB { } } + public static void createGpsNotification(Context context, int numDevices) { + Intent notificationIntent = new Intent(context, ControlCenterv2.class); + notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0); + + NotificationCompat.Builder nb = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID_GPS) + .setTicker(context.getString(R.string.notification_gps_title)) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setContentTitle(context.getString(R.string.notification_gps_title)) + .setContentText(context.getString(R.string.notification_gps_text, numDevices)) + .setContentIntent(pendingIntent) + .setSmallIcon(R.drawable.ic_gps_location) + .setOngoing(true); + + notify(NOTIFICATION_ID_GPS, nb.build(), context); + } + + public static void removeGpsNotification(Context context) { + removeNotification(NOTIFICATION_ID_GPS, context); + } + private static Notification createInstallNotification(String text, boolean ongoing, int percentage, Context context) { Intent notificationIntent = new Intent(context, ControlCenterv2.class); diff --git a/app/src/main/res/drawable/ic_gps_location.xml b/app/src/main/res/drawable/ic_gps_location.xml new file mode 100644 index 0000000000..8173d3d4bc --- /dev/null +++ b/app/src/main/res/drawable/ic_gps_location.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_debug.xml b/app/src/main/res/layout/activity_debug.xml index 19de06c289..ec93917b2c 100644 --- a/app/src/main/res/layout/activity_debug.xml +++ b/app/src/main/res/layout/activity_debug.xml @@ -256,6 +256,14 @@ android:text="Show Fit.App.Track. Status" grid:layout_columnSpan="2" grid:layout_gravity="fill_horizontal" /> + +