1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-02 19:36:14 +02:00

Compare commits

..

35 Commits

Author SHA1 Message Date
kuhy
da9f455ab8 Garmin protocol: improve detection of successfully sent files (DataTransferHandler) 2024-04-25 18:21:46 +02:00
kuhy
9bf92972ba Garmin protocol: add support for AGPS data retrieval 2024-04-24 23:23:58 +02:00
José Rebelo
438bfa4cce Garmin: Map all known files types 2024-04-23 20:06:51 +01:00
José Rebelo
13c72d827f Garmin: Add support for http weather requests 2024-04-23 20:06:50 +01:00
Daniele Gobbetti
1a120bcd50 Garmin: Rename LocalMessage to PredefinedLocalMessage and clarify its usage
PredefinedLocalMessage are only useful for FIT messages and should not interfere with FIT files. The only impact of using the local message in fit files was in the textual output, but it was confusing.

Add an explicit constructor to RecordHeader if PredefinedLocalMessage should be taken into account, and use this only in fit messages leaving the default constructor for fit files.

Also adjusts the test case as textual output comparison needs to be fixed.
2024-04-23 16:39:59 +02:00
kuhy
9b27a71328 Initial support for Garmin Vivoactive 4S 2024-04-23 16:13:39 +02:00
Daniele Gobbetti
ba560a9a4f Garmin: Add support for custom replies (notifications and calls)
To enable custom replies an override must be defined in the devices coordinator that actually support custom replies.

The custom preferences allow to:
- enable / disable the default message suffix (Instinct 2 appends "sent from my $vendor device" to each reply by default)
- define custom messages to reply to calls and incoming messages (leaving those lists empty will enable the default messages to be used)

Also adds a new protobuf definition file of mostly unknown values that enable toggling the message suffix on Instinct 2.
2024-04-23 16:11:37 +02:00
myxor
f0efc53d54 Initial support for Garmin Vivoactive 5 2024-04-21 11:12:57 +02:00
Daniele Gobbetti
f127c47fe9 Garmin: Add support for replying to notifications
This uses the (assumed) new method of passing multiple actions, instead of the (assumed) legacy accept/decline approach.
At the moment the preset messages stored on the watch firmware are used for replying, the code supports using custom messages already but those have to be updated to the watch somehow (probably by protobuf) and this is not supported yet. Using custom messages if they are not set will just do nothing.
The NotificationActionIconPosition values have been determined on a vĂ­vomove Style and might not work properly on other watches.
The evaluation of GBDeviceEvent have been moved in GarminSupport since the notification actions handling uses device events.

Also adds a method to read null terminated strings to GarminByteBufferReader.
Also adds a warning in NotificationListener if the wrong handle is used for replying to a notification.
2024-04-20 16:37:50 +02:00
Daniele Gobbetti
4c25ae2d83 Garmin: Add FileDownloadedDeviceEvent and (disabled) file deletion
Also adds (disabled) file deletion in case of already downloaded files
2024-04-19 17:07:27 +02:00
Daniele Gobbetti
b2d6f4492a Garmin: Add DST/Timezone support 2024-04-19 16:45:58 +02:00
hrdl
1a9fe65a52 Add Garmin Forerunner 245 2024-04-19 13:07:56 +02:00
Daniele Gobbetti
9cc5635474 Garmin: Support file archival (deletion) on watch
Also add original timestamp to local cache filename as the file identifier are reused
Also fix imports of Test class
2024-04-18 17:57:05 +02:00
José Rebelo
be66a73fd1 Garmin: Fetch activity on demand 2024-04-18 17:57:02 +02:00
José Rebelo
930bd208ae Garmin: Fix proguard rules for release builds 2024-04-18 17:50:17 +02:00
José Rebelo
6074406ccc Garmin: Allow high MTU 2024-04-18 17:50:17 +02:00
José Rebelo
c710f92dc0 Garmin protocol: Simplify FILE_TYPE 2024-04-18 17:50:17 +02:00
José Rebelo
7afb85ba03 Garmin protocol: Fix linter warnings 2024-04-18 17:50:17 +02:00
José Rebelo
4a15e1aa92 Garmin protocol: Introduce GarminCoordinator 2024-04-18 17:50:17 +02:00
José Rebelo
2dd6859648 Garmin protocol: fix crash when stopping find phone 2024-04-18 17:50:17 +02:00
Daniele Gobbetti
7829c5f1fb Garmin protocol: basic file transfer and notification handling
adds synchronization of supported files from watch to external directory
adds support for Activity and Monitoring files (workouts and activity samples), but those are not integrated yet
adds upload functionality (not used ATM and not tested)
adds notification support without actions
introduces centralized processing of "messageHandlers" (protobuf, file transfer, notifications)

also properly dispose of the music timer when disconnecting
2024-04-18 17:50:17 +02:00
Daniele Gobbetti
5a273c1118 Garmin protocol: enable media volume control from watch 2024-04-18 17:50:17 +02:00
Daniele Gobbetti
2473ae2f52 Garmin protocol: store max packet size from DeviceInformationMessage
also adds messageType to the warnifleftover log message
2024-04-18 17:50:17 +02:00
Daniele Gobbetti
f94299fcc1 Garmin protocol: various changes
- add FitFile class that deals with parsing and generating outgoing files
- consider all field definitions with number 253 as Timestamps [0]
- add support for "compressed timestamps" in fit file parsing. Those are not returned among the other normal fields but are available through a method of RecordData
- adjust the test cases

[0]48b6554d8a/fitdecode/reader.py (L719)
2024-04-18 17:50:16 +02:00
Daniele Gobbetti
f05b7f44a9 Garmin protocol: change naming and logic of several FIT classes
- refactor the logic of Global and Local messages
- add some Global messages with naming taken from [1]
- Global messages are not enum because there are too many
- introduce the concept of FieldDefinitionPrimitive
- add new Field Definitions
- add support for developer fields and array fields
- add test case for FIT files taken from [0]

[0] https://github.com/polyvertex/fitdecode/
[1] https://www.fitfileviewer.com/
2024-04-18 17:50:16 +02:00
Daniele Gobbetti
fc5b8c5641 Garmin protocol: create helper class GarminByteBufferReader
separate the logic specific for GFDI messages from the generally useful logic.
Also centralize the logging in case of leftover bytes while parsing GFDI messages.
2024-04-18 17:50:16 +02:00
Daniele Gobbetti
944b1025c2 Garmin protocol: create custom GBDeviceEvent for weather request 2024-04-18 17:50:16 +02:00
Daniele Gobbetti
e81404379e Garmin protocol: use message enum instead of id in GFDI Messages 2024-04-18 17:50:16 +02:00
Daniele Gobbetti
cebcd24c68 Garmin protocol: refactoring and fixes of BaseTypes
The boundaries are enforced on the stored value when decoding, before applying the adjustments for scale and offset.
Also add some tests for the BaseTypes
Introduce new FieldDefinition for Temperature and WeatherCondition (removing the static class)
Add accessors for field data in the containing RecordData, thus keeping the FieldData private
2024-04-18 17:50:16 +02:00
Daniele Gobbetti
1d8cd7dd1e Garmin protocol: create specific field definition for day of week 2024-04-18 17:50:16 +02:00
Daniele Gobbetti
1f7f502fe9 Garmin protocol: move field encode/decode interface to the FieldDefinition
This allows for semantic subclassing the FieldDefinition.
A FieldDefinitionTimestamp subclass is introduced as example
2024-04-18 17:50:16 +02:00
Daniele Gobbetti
d00b94f333 Garmin protocol: fix invalid signed int base type value 2024-04-18 17:50:16 +02:00
Daniele Gobbetti
16354d333e Garmin protocol: add initial support for FIT messages
note: only weather message definition and data tested so far
also enable weather support for Instinct 2S and vivomove style
also cleanup some unused constants that have been migrated to new enums in GFDIMessage
additionally switch to new local implementation of GarminTimeUtils with needed methods
2024-04-18 17:50:16 +02:00
Daniele Gobbetti
8107002a87 Garmin protocol: fixes
- fix DEVICE_SETTINGS message ID
- put all status messages in own package
- allow protobuf handler to change the returned status message to signal unsupported requests
- fix various bugs
2024-04-18 17:50:16 +02:00
Daniele Gobbetti
f1111d3790 Garmin protocol: initial refactoring and basic functionalities
This commit takes aims to bring many new garmin devices up to a working status, with basic functionalities such as:
- garmin protocol initialization
- basic message exchange
- support for some messages in Garmin own format
- support for some messages in protobuf format
2024-04-18 17:50:15 +02:00
80 changed files with 558 additions and 2387 deletions

View File

@ -0,0 +1,2 @@
connection.project.dir=
eclipse.preferences.version=1

View File

@ -110,24 +110,6 @@
android:requestLegacyExternalStorage="true"
android:theme="@style/GadgetbridgeTheme"
tools:replace="android:label">
<receiver
android:name=".externalevents.sleepasandroid.SleepAsAndroidReceiver"
android:enabled="true"
android:exported="true" >
<intent-filter>
<action android:name="com.urbandroid.sleep.watch.START_TRACKING" />
<action android:name="com.urbandroid.sleep.watch.STOP_TRACKING" />
<action android:name="com.urbandroid.sleep.watch.SET_PAUSE" />
<action android:name="com.urbandroid.sleep.watch.SET_SUSPENDED" />
<action android:name="com.urbandroid.sleep.watch.SET_BATCH_SIZE" />
<action android:name="com.urbandroid.sleep.watch.START_ALARM" />
<action android:name="com.urbandroid.sleep.watch.STOP_ALARM" />
<action android:name="com.urbandroid.sleep.watch.UPDATE_ALARM" />
<action android:name="com.urbandroid.sleep.watch.SHOW_NOTIFICATION" />
<action android:name="com.urbandroid.sleep.watch.HINT" />
<action android:name="com.urbandroid.sleep.watch.CHECK_CONNECTED" />
</intent-filter>
</receiver>
<activity
android:name=".activities.ControlCenterv2"
android:label="@string/title_activity_controlcenter"
@ -150,10 +132,6 @@
android:name=".activities.DashboardPreferencesActivity"
android:label="@string/dashboard_settings"
android:parentActivityName=".activities.SettingsActivity" />
<activity
android:name=".activities.SleepAsAndroidPreferencesActivity"
android:label="@string/sleepasandroid_settings"
android:parentActivityName=".activities.SettingsActivity" />
<activity
android:name=".activities.AboutUserPreferencesActivity"
android:label="@string/activity_prefs_about_you"
@ -881,4 +859,4 @@
android:parentActivityName=".activities.ControlCenterv2" />
</application>
</manifest>
</manifest>

View File

@ -106,7 +106,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.GBLocationService;
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;
@ -220,12 +220,6 @@ public class DebugActivity extends AbstractGBActivity {
replyAction.title = "Reply";
replyAction.type = NotificationSpec.Action.TYPE_SYNTECTIC_REPLY_PHONENR;
notificationSpec.attachedActions.add(replyAction);
} else if (notificationSpec.type == NotificationType.CONVERSATIONS) {
// REPLY action
NotificationSpec.Action replyAction = new NotificationSpec.Action();
replyAction.title = "Reply";
replyAction.type = NotificationSpec.Action.TYPE_WEARABLE_REPLY;
notificationSpec.attachedActions.add(replyAction);
}
GBApplication.deviceService().onNotification(notificationSpec);
@ -665,7 +659,7 @@ public class DebugActivity extends AbstractGBActivity {
stopPhoneGpsLocationListener.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GBLocationService.stop(DebugActivity.this, null);
GBLocationManager.stopAll(getBaseContext());
}
});

View File

@ -421,15 +421,6 @@ public class SettingsActivity extends AbstractSettingsActivityV2 {
});
}
pref = findPreference("pref_category_sleepasandroid");
if (pref != null) {
pref.setOnPreferenceClickListener(preference -> {
Intent enableIntent = new Intent(requireContext(), SleepAsAndroidPreferencesActivity.class);
startActivity(enableIntent);
return true;
});
}
final Preference theme = findPreference("pref_key_theme");
final Preference amoled_black = findPreference("pref_key_theme_amoled_black");

View File

@ -1,138 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.devices.SleepAsAndroidFeature;
public class SleepAsAndroidPreferencesActivity extends AbstractSettingsActivityV2 {
@Override
protected String fragmentTag() {
return SleepAsAndroidPreferencesFragment.FRAGMENT_TAG;
}
@Override
protected PreferenceFragmentCompat newFragment() {
return new SleepAsAndroidPreferencesFragment();
}
public static class SleepAsAndroidPreferencesFragment extends AbstractPreferenceFragment {
static final String FRAGMENT_TAG = "SLEEPASANDROID_PREFERENCES_FRAGMENT";
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
setPreferencesFromResource(R.xml.sleepasandroid_preferences, rootKey);
final ListPreference sleepAsAndroidSlots = findPreference("sleepasandroid_alarm_slot");
if (sleepAsAndroidSlots != null)
{
loadAlarmSlots(sleepAsAndroidSlots);
}
final ListPreference sleepAsAndroidDevices = findPreference("sleepasandroid_device");
if (sleepAsAndroidDevices != null) {
loadDevicesList(sleepAsAndroidDevices);
sleepAsAndroidDevices.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
GBDevice device = GBApplication.app().getDeviceManager().getDeviceByAddress(newValue.toString());
if (device != null) {
GBApplication.getPrefs().getPreferences().edit().putString("sleepasandroid_device", device.getAddress()).apply();
Set<SleepAsAndroidFeature> supportedFeatures = device.getDeviceCoordinator().getSleepAsAndroidFeatures();
findPreference("sleepasandroid_alarm_slot").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.ALARMS));
findPreference("pref_key_sleepasandroid_feat_alarms").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.ALARMS));
findPreference("pref_key_sleepasandroid_feat_notifications").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.NOTIFICATIONS));
findPreference("pref_key_sleepasandroid_feat_movement").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.ACCELEROMETER));
findPreference("pref_key_sleepasandroid_feat_hr").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.HEART_RATE));
findPreference("pref_key_sleepasandroid_feat_oximetry").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.OXIMETRY));
findPreference("pref_key_sleepasandroid_feat_spo2").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.SPO2));
ListPreference alarmSlots = findPreference("sleepasandroid_alarm_slot");
if (alarmSlots != null)
{
loadAlarmSlots(alarmSlots);
if (alarmSlots.getEntries().length > 0)
{
alarmSlots.setValueIndex(0);
GB.toast(getString(R.string.alarm_slot_reset), Toast.LENGTH_SHORT, GB.WARN);
}
}
}
return false;
}
});
}
String defaultDeviceAddr = GBApplication.getPrefs().getString("sleepasandroid_device", "");
if (!defaultDeviceAddr.isEmpty()) {
GBDevice device = GBApplication.app().getDeviceManager().getDeviceByAddress(defaultDeviceAddr);
if (device != null) {
Set<SleepAsAndroidFeature> supportedFeatures = device.getDeviceCoordinator().getSleepAsAndroidFeatures();
findPreference("sleepasandroid_alarm_slot").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.ALARMS));
findPreference("pref_key_sleepasandroid_feat_alarms").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.ALARMS));
findPreference("pref_key_sleepasandroid_feat_notifications").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.NOTIFICATIONS));
findPreference("pref_key_sleepasandroid_feat_movement").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.ACCELEROMETER));
findPreference("pref_key_sleepasandroid_feat_hr").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.HEART_RATE));
findPreference("pref_key_sleepasandroid_feat_oximetry").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.OXIMETRY));
findPreference("pref_key_sleepasandroid_feat_spo2").setEnabled(supportedFeatures.contains(SleepAsAndroidFeature.SPO2));
}
}
}
}
private static void loadAlarmSlots(ListPreference sleepAsAndroidSlots) {
if (sleepAsAndroidSlots != null) {
String defaultDeviceAddr = GBApplication.getPrefs().getString("sleepasandroid_device", "");
if (!defaultDeviceAddr.isEmpty()) {
GBDevice device = GBApplication.app().getDeviceManager().getDeviceByAddress(defaultDeviceAddr);
if (device != null) {
int maxAlarmSlots = device.getDeviceCoordinator().getAlarmSlotCount(device);
if (maxAlarmSlots > 0) {
List<String> alarmSlots = new ArrayList<>();
int reservedAlarmSlots = GBApplication.getPrefs().getInt(DeviceSettingsPreferenceConst.PREF_RESERVER_ALARMS_CALENDAR, 0);
for (int i = reservedAlarmSlots + 1;i < maxAlarmSlots; i++) {
alarmSlots.add(String.valueOf(i));
}
sleepAsAndroidSlots.setEntryValues(alarmSlots.toArray(new String[0]));
sleepAsAndroidSlots.setEntries(alarmSlots.toArray(new String[0]));
}
}
}
}
}
private static void loadDevicesList(ListPreference sleepAsAndroidDevices) {
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
List<String> deviceMACs = new ArrayList<>();
List<String> deviceNames = new ArrayList<>();
for (GBDevice dev : devices) {
if (dev.getDeviceCoordinator().supportsSleepAsAndroid()) {
deviceMACs.add(dev.getAddress());
deviceNames.add(dev.getAliasOrName());
}
}
sleepAsAndroidDevices.setEntryValues(deviceMACs.toArray(new String[0]));
sleepAsAndroidDevices.setEntries(deviceNames.toArray(new String[0]));
}
}

View File

@ -137,9 +137,6 @@ public class DashboardTodayWidget extends AbstractDashboardWidget {
}
private void draw() {
Prefs prefs = GBApplication.getPrefs();
boolean upsideDown24h = prefs.getBoolean("dashboard_widget_today_24h_upside_down", false);
// Prepare circular chart
long midDaySecond = dashboardData.timeFrom + (12 * 60 * 60);
int width = 500;
@ -186,16 +183,7 @@ public class DashboardTodayWidget extends AbstractDashboardWidget {
textPaint.setTextSize(hourTextPixels);
textPaint.setTextAlign(Paint.Align.CENTER);
Rect textBounds = new Rect();
if (mode_24h && upsideDown24h) {
textPaint.getTextBounds(hours.get(6), 0, hours.get(6).length(), textBounds);
canvas.drawText(hours.get(6), clockMargin + clockStripesWidth + textBounds.width() / 2f, height / 2f + textBounds.height() / 2f, textPaint);
textPaint.getTextBounds(hours.get(12), 0, hours.get(12).length(), textBounds);
canvas.drawText(hours.get(12), width / 2f, clockMargin + clockStripesWidth + textBounds.height(), textPaint);
textPaint.getTextBounds(hours.get(18), 0, hours.get(18).length(), textBounds);
canvas.drawText(hours.get(18), width - (clockMargin + clockStripesWidth + textBounds.width()), height / 2f + textBounds.height() / 2f, textPaint);
textPaint.getTextBounds(hours.get(24), 0, hours.get(24).length(), textBounds);
canvas.drawText(hours.get(24), width / 2f, height - (clockMargin + clockStripesWidth), textPaint);
} else if (mode_24h) {
if (mode_24h) {
textPaint.getTextBounds(hours.get(6), 0, hours.get(6).length(), textBounds);
canvas.drawText(hours.get(6), width - (clockMargin + clockStripesWidth + textBounds.width()), height / 2f + textBounds.height() / 2f, textPaint);
textPaint.getTextBounds(hours.get(12), 0, hours.get(12).length(), textBounds);
@ -228,7 +216,6 @@ public class DashboardTodayWidget extends AbstractDashboardWidget {
// Draw generalized activities on circular chart
long secondIndex = dashboardData.timeFrom;
long currentTime = Calendar.getInstance().getTimeInMillis() / 1000;
int startAngle = mode_24h && upsideDown24h ? 90 : 270;
synchronized (dashboardData.generalizedActivities) {
for (DashboardFragment.DashboardData.GeneralizedActivity activity : dashboardData.generalizedActivities) {
// Determine margin depending on 24h/12h mode
@ -237,15 +224,15 @@ public class DashboardTodayWidget extends AbstractDashboardWidget {
if (!mode_24h && secondIndex < midDaySecond && activity.timeFrom >= midDaySecond) {
paint.setStrokeWidth(barWidth / 3f);
paint.setColor(color_unknown);
canvas.drawArc(innerCircleMargin, innerCircleMargin, width - innerCircleMargin, height - innerCircleMargin, startAngle + (secondIndex - dashboardData.timeFrom) / degreeFactor, (midDaySecond - secondIndex) / degreeFactor, false, paint);
canvas.drawArc(innerCircleMargin, innerCircleMargin, width - innerCircleMargin, height - innerCircleMargin, 270 + (secondIndex - dashboardData.timeFrom) / degreeFactor, (midDaySecond - secondIndex) / degreeFactor, false, paint);
secondIndex = midDaySecond;
}
if (activity.timeFrom > secondIndex) {
paint.setStrokeWidth(barWidth / 3f);
paint.setColor(color_unknown);
canvas.drawArc(margin, margin, width - margin, height - margin, startAngle + (secondIndex - dashboardData.timeFrom) / degreeFactor, (activity.timeFrom - secondIndex) / degreeFactor, false, paint);
canvas.drawArc(margin, margin, width - margin, height - margin, 270 + (secondIndex - dashboardData.timeFrom) / degreeFactor, (activity.timeFrom - secondIndex) / degreeFactor, false, paint);
}
float start_angle = startAngle + (activity.timeFrom - dashboardData.timeFrom) / degreeFactor;
float start_angle = 270 + (activity.timeFrom - dashboardData.timeFrom) / degreeFactor;
float sweep_angle = (activity.timeTo - activity.timeFrom) / degreeFactor;
if (activity.activityKind == ActivityKind.TYPE_NOT_MEASURED) {
paint.setStrokeWidth(barWidth / 3f);
@ -284,11 +271,11 @@ public class DashboardTodayWidget extends AbstractDashboardWidget {
// Fill inner bar up until current time
paint.setStrokeWidth(barWidth / 3f);
paint.setColor(color_unknown);
canvas.drawArc(innerCircleMargin, innerCircleMargin, width - innerCircleMargin, height - innerCircleMargin, startAngle + (secondIndex - dashboardData.timeFrom) / degreeFactor, (currentTime - secondIndex) / degreeFactor, false, paint);
canvas.drawArc(innerCircleMargin, innerCircleMargin, width - innerCircleMargin, height - innerCircleMargin, 270 + (secondIndex - dashboardData.timeFrom) / degreeFactor, (currentTime - secondIndex) / degreeFactor, false, paint);
// Fill inner bar up until midday
paint.setStrokeWidth(barWidth / 3f);
paint.setColor(color_unknown);
canvas.drawArc(innerCircleMargin, innerCircleMargin, width - innerCircleMargin, height - innerCircleMargin, startAngle + (currentTime - dashboardData.timeFrom) / degreeFactor, (midDaySecond - currentTime) / degreeFactor, false, paint);
canvas.drawArc(innerCircleMargin, innerCircleMargin, width - innerCircleMargin, height - innerCircleMargin, 270 + (currentTime - dashboardData.timeFrom) / degreeFactor, (midDaySecond - currentTime) / degreeFactor, false, paint);
// Fill outer bar up until midnight
paint.setStrokeWidth(barWidth / 3f);
paint.setColor(color_unknown);
@ -300,24 +287,24 @@ public class DashboardTodayWidget extends AbstractDashboardWidget {
if (!mode_24h && secondIndex < midDaySecond) {
paint.setStrokeWidth(barWidth / 3f);
paint.setColor(color_unknown);
canvas.drawArc(innerCircleMargin, innerCircleMargin, width - innerCircleMargin, height - innerCircleMargin, startAngle + (secondIndex - dashboardData.timeFrom) / degreeFactor, (midDaySecond - secondIndex) / degreeFactor, false, paint);
canvas.drawArc(innerCircleMargin, innerCircleMargin, width - innerCircleMargin, height - innerCircleMargin, 270 + (secondIndex - dashboardData.timeFrom) / degreeFactor, (midDaySecond - secondIndex) / degreeFactor, false, paint);
secondIndex = midDaySecond;
}
// Fill outer bar up until current time
paint.setStrokeWidth(barWidth / 3f);
paint.setColor(color_unknown);
canvas.drawArc(outerCircleMargin, outerCircleMargin, width - outerCircleMargin, height - outerCircleMargin, startAngle + (secondIndex - dashboardData.timeFrom) / degreeFactor, (currentTime - secondIndex) / degreeFactor, false, paint);
canvas.drawArc(outerCircleMargin, outerCircleMargin, width - outerCircleMargin, height - outerCircleMargin, 270 + (secondIndex - dashboardData.timeFrom) / degreeFactor, (currentTime - secondIndex) / degreeFactor, false, paint);
// Fill outer bar up until midnight
paint.setStrokeWidth(barWidth / 3f);
paint.setColor(color_unknown);
canvas.drawArc(outerCircleMargin, outerCircleMargin, width - outerCircleMargin, height - outerCircleMargin, startAngle + (currentTime - dashboardData.timeFrom) / degreeFactor, (dashboardData.timeTo - currentTime) / degreeFactor, false, paint);
canvas.drawArc(outerCircleMargin, outerCircleMargin, width - outerCircleMargin, height - outerCircleMargin, 270 + (currentTime - dashboardData.timeFrom) / degreeFactor, (dashboardData.timeTo - currentTime) / degreeFactor, false, paint);
}
// Only when displaying a past day
if (secondIndex < dashboardData.timeTo && currentTime > dashboardData.timeTo) {
// Fill outer bar up until midnight
paint.setStrokeWidth(barWidth / 3f);
paint.setColor(color_unknown);
canvas.drawArc(outerCircleMargin, outerCircleMargin, width - outerCircleMargin, height - outerCircleMargin, startAngle + (secondIndex - dashboardData.timeFrom) / degreeFactor, (dashboardData.timeTo - secondIndex) / degreeFactor, false, paint);
canvas.drawArc(outerCircleMargin, outerCircleMargin, width - outerCircleMargin, height - outerCircleMargin, 270 + (secondIndex - dashboardData.timeFrom) / degreeFactor, (dashboardData.timeTo - secondIndex) / degreeFactor, false, paint);
}
todayChart.setImageBitmap(todayBitmap);

View File

@ -356,7 +356,7 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
});
}
addPreferenceHandlerFor(PREF_SEND_APP_NOTIFICATIONS);
addPreferenceHandlerFor(PREF_SWIPE_UNLOCK);
addPreferenceHandlerFor(PREF_MI2_DATEFORMAT);
addPreferenceHandlerFor(PREF_DATEFORMAT);

View File

@ -30,7 +30,6 @@ public enum DeviceSpecificSettingsScreen {
DEVELOPER("pref_screen_developer", R.xml.devicesettings_root_developer),
DISPLAY("pref_screen_display", R.xml.devicesettings_root_display),
GENERIC("pref_screen_generic", R.xml.devicesettings_root_generic),
LOCATION("pref_screen_location", R.xml.devicesettings_root_location),
NOTIFICATIONS("pref_screen_notifications", R.xml.devicesettings_root_notifications),
DATE_TIME("pref_screen_date_time", R.xml.devicesettings_root_date_time),
WORKOUT("pref_screen_workout", R.xml.devicesettings_root_workout),

View File

@ -843,6 +843,7 @@ public class GBDeviceAdapterv2 extends ListAdapter<GBDevice, GBDeviceAdapterv2.V
private void showDeviceSubmenu(final View v, final GBDevice device) {
boolean deviceConnected = device.getState() != GBDevice.State.NOT_CONNECTED;
PopupMenu menu = new PopupMenu(v.getContext(), v);
menu.inflate(R.menu.fragment_devices_device_submenu);

View File

@ -78,7 +78,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@ -587,16 +586,6 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return false;
}
@Override
public boolean supportsSleepAsAndroid() {
return false;
}
@Override
public Set<SleepAsAndroidFeature> getSleepAsAndroidFeatures() {
return Collections.emptySet();
}
@Override
public int[] getSupportedDeviceSpecificConnectionSettings() {
int[] settings = new int[0];

View File

@ -29,7 +29,6 @@ import java.io.IOException;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
@ -58,7 +57,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
/**
* This interface is implemented at least once for every supported gadget device.
@ -513,11 +511,6 @@ public interface DeviceCoordinator {
*/
boolean supportsMusicInfo();
/**
* Indicates whether the device supports features required by Sleep As Android
*/
boolean supportsSleepAsAndroid();
/**
* Indicates the maximum reminder message length.
*/
@ -576,12 +569,6 @@ public interface DeviceCoordinator {
*/
boolean supportsUnicodeEmojis();
/**
* Returns the set of supported sleep as Android features
* @return Set
*/
Set<SleepAsAndroidFeature> getSleepAsAndroidFeatures();
/**
* Returns device specific settings related to connection
*

View File

@ -20,7 +20,6 @@ package nodomain.freeyourgadget.gadgetbridge.devices;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.UUID;
@ -149,6 +148,4 @@ public interface EventHandler {
void onPowerOff();
void onSetGpsLocation(Location location);
void onSleepAsAndroidAction(String action, Bundle extras);
}

View File

@ -1,11 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.devices;
public enum SleepAsAndroidFeature {
HEART_RATE,
ALARMS,
NOTIFICATIONS,
ACCELEROMETER,
OXIMETRY,
SPO2
}

View File

@ -1,122 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
import android.content.Context;
import android.net.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.file.GarminAgpsFile;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
public class GarminAgpsInstallHandler implements InstallHandler {
private static final Logger LOG = LoggerFactory.getLogger(GarminAgpsInstallHandler.class);
protected final Context mContext;
private GarminAgpsFile file;
public GarminAgpsInstallHandler(final Uri uri, final Context context) {
this.mContext = context;
final UriHelper uriHelper;
try {
uriHelper = UriHelper.get(uri, context);
} catch (final IOException e) {
LOG.error("Failed to get uri", e);
return;
}
try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
final byte[] rawBytes = FileUtils.readAll(in, 1024 * 1024); // 1MB, they're usually ~60KB
final GarminAgpsFile agpsFile = new GarminAgpsFile(rawBytes);
if (agpsFile.isValid()) {
this.file = agpsFile;
}
} catch (final Exception e) {
LOG.error("Failed to read file", e);
}
}
@Override
public boolean isValid() {
return file != null;
}
@Override
public void validateInstallation(final InstallActivity installActivity, final GBDevice device) {
if (device.isBusy()) {
installActivity.setInfoText(device.getBusyTask());
installActivity.setInstallEnabled(false);
return;
}
final DeviceCoordinator coordinator = device.getDeviceCoordinator();
if (!(coordinator instanceof GarminCoordinator)) {
LOG.warn("Coordinator is not a GarminCoordinator: {}", coordinator.getClass());
installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
installActivity.setInstallEnabled(false);
return;
}
final GarminCoordinator garminCoordinator = (GarminCoordinator) coordinator;
if (!garminCoordinator.supportsAgpsUpdates()) {
installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported));
installActivity.setInstallEnabled(false);
return;
}
if (!device.isInitialized()) {
installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready));
installActivity.setInstallEnabled(false);
return;
}
final GenericItem fwItem = createInstallItem(device);
fwItem.setIcon(coordinator.getDefaultIconResource());
if (file == null) {
fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_incompatible_version));
installActivity.setInfoText(mContext.getString(R.string.fwinstaller_firmware_not_compatible_to_device));
installActivity.setInstallEnabled(false);
return;
}
final StringBuilder builder = new StringBuilder();
final String agpsBundle = mContext.getString(R.string.kind_agps_bundle);
builder.append(mContext.getString(R.string.fw_upgrade_notice, agpsBundle));
builder.append("\n\n").append(mContext.getString(R.string.miband_firmware_unknown_warning));
fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_untested_version));
installActivity.setInfoText(builder.toString());
installActivity.setInstallItem(fwItem);
installActivity.setInstallEnabled(true);
}
@Override
public void onStartInstall(final GBDevice device) {
}
public GarminAgpsFile getFile() {
return file;
}
private GenericItem createInstallItem(final GBDevice device) {
DeviceCoordinator coordinator = device.getDeviceCoordinator();
final String firmwareName = mContext.getString(
R.string.installhandler_firmware_name,
mContext.getString(coordinator.getDeviceNameResource()),
mContext.getString(R.string.kind_agps_bundle),
""
);
return new GenericItem(firmwareName);
}
}

View File

@ -1,25 +1,19 @@
package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
@Override
@ -42,22 +36,17 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
notifications.add(R.xml.devicesettings_send_app_notifications);
final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
connection.add(R.xml.devicesettings_high_mtu);
if (getCannedRepliesSlotCount(device) > 0) {
final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS);
notifications.add(R.xml.devicesettings_garmin_default_reply_suffix);
notifications.add(R.xml.devicesettings_canned_reply_16);
notifications.add(R.xml.devicesettings_canned_dismisscall_16);
}
final List<Integer> location = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.LOCATION);
location.add(R.xml.devicesettings_workout_send_gps_to_band);
final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
connection.add(R.xml.devicesettings_high_mtu);
final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
developer.add(R.xml.devicesettings_keep_activity_data_on_device);
@ -78,32 +67,4 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
public boolean supportsWeather() {
return true;
}
@Override
public int getCannedRepliesSlotCount(final GBDevice device) {
if (getPrefs(device).getBoolean(GarminPreferences.PREF_FEAT_CANNED_MESSAGES, false)) {
return 16;
}
return 0;
}
protected static Prefs getPrefs(final GBDevice device) {
return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
}
public InstallHandler findInstallHandler(final Uri uri, final Context context) {
if (supportsAgpsUpdates()) {
final GarminAgpsInstallHandler agpsInstallHandler = new GarminAgpsInstallHandler(uri, context);
if (agpsInstallHandler.isValid()) {
return agpsInstallHandler;
}
}
return null;
}
public boolean supportsAgpsUpdates() {
return false;
}
}

View File

@ -2,5 +2,4 @@ package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
public class GarminPreferences {
public static final String PREF_GARMIN_CAPABILITIES = "garmin_capabilities";
public static final String PREF_FEAT_CANNED_MESSAGES = "feat_canned_messages";
}

View File

@ -4,6 +4,7 @@ import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GarminInstinct2SCoordinator extends GarminCoordinator {
@Override
@ -11,6 +12,11 @@ public class GarminInstinct2SCoordinator extends GarminCoordinator {
return Pattern.compile("Instinct 2S");
}
@Override
public int getCannedRepliesSlotCount(final GBDevice device) {
return 16;
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_garmin_instinct_2s;

View File

@ -1,19 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.devices.garmin.instinct2solar;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import java.util.regex.Pattern;
public class GarminInstinct2SolarCoordinator extends GarminCoordinator {
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("Instinct 2 Solar");
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_garmin_instinct_2_solar;
}
}

View File

@ -1,18 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.devices.garmin.instinct2soltac;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
public class GarminInstinct2SolTacCoordinator extends GarminCoordinator {
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("Instinct 2 SolTac");
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_garmin_instinct_2_soltac;
}
}

View File

@ -6,23 +6,13 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
public class GarminVivoActive4SCoordinator extends GarminCoordinator {
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("vĂ­voactive 4S");
}
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("vĂ­voactive 4S");
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_garmin_vivoactive_4s;
}
@Override
public boolean supportsFlashing() {
return true;
}
@Override
public boolean supportsAgpsUpdates() {
return true;
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_garmin_vivoactive_4s;
}
}

View File

@ -27,8 +27,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -45,7 +43,6 @@ import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabi
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiExtendedSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
@ -76,7 +73,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibration
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsPhoneService;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.devices.SleepAsAndroidFeature;
public abstract class ZeppOsCoordinator extends HuamiCoordinator {
public abstract String getDeviceBluetoothName();
@ -200,16 +196,6 @@ public abstract class ZeppOsCoordinator extends HuamiCoordinator {
return true;
}
@Override
public boolean supportsSleepAsAndroid() {
return true;
}
@Override
public Set<SleepAsAndroidFeature> getSleepAsAndroidFeatures() {
return EnumSet.of(SleepAsAndroidFeature.ACCELEROMETER, SleepAsAndroidFeature.HEART_RATE, SleepAsAndroidFeature.ALARMS, SleepAsAndroidFeature.NOTIFICATIONS);
}
@Override
public int getWorldClocksSlotCount() {
return 20; // as enforced by Zepp

View File

@ -30,7 +30,6 @@ import java.util.Collections;
import java.util.List;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
@ -84,11 +83,6 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
return huaweiCoordinator.getSupportedLanguageSettings(device);
}
@Override
public int[] getSupportedDeviceSpecificAuthenticationSettings() {
return new int[]{R.xml.devicesettings_huawei_account};
}
@Override
public int getBondingStyle(){
return BONDING_STYLE_ASK;

View File

@ -71,7 +71,6 @@ public final class HuaweiConstants {
public static final String PREF_HUAWEI_ADDRESS = "huawei_address";
public static final String PREF_HUAWEI_WORKMODE = "workmode";
public static final String PREF_HUAWEI_TRUSLEEP = "trusleep";
public static final String PREF_HUAWEI_ACCOUNT = "huawei_account";
public static final String PREF_HUAWEI_DND_LIFT_WRIST_TYPE = "dnd_lift_wrist_type"; // SharedPref for 0x01 0x1D
public static final String PREF_HUAWEI_DEBUG_REQUEST = "debug_huawei_request";

View File

@ -30,7 +30,6 @@ import java.util.Collections;
import java.util.List;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.GBException;
@ -83,12 +82,7 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
public String[] getSupportedLanguageSettings(GBDevice device) {
return huaweiCoordinator.getSupportedLanguageSettings(device);
}
@Override
public int[] getSupportedDeviceSpecificAuthenticationSettings() {
return new int[]{R.xml.devicesettings_huawei_account};
}
@Override
public int getBondingStyle(){
return BONDING_STYLE_NONE;

View File

@ -26,18 +26,15 @@ public class AccountRelated {
public static final byte id = 0x01;
public static class Request extends HuaweiPacket {
public Request (ParamsProvider paramsProvider, String account) {
public Request (ParamsProvider paramsProvider) {
super(paramsProvider);
this.serviceId = AccountRelated.id;
this.commandId = id;
this.tlv = new HuaweiTLV();
if (account.length() > 0) {
tlv.put(0x01, account);
} else {
tlv.put(0x01);
}
this.tlv = new HuaweiTLV()
.put(0x01);
this.complete = true;
}
}
@ -53,19 +50,14 @@ public class AccountRelated {
public static final byte id = 0x05;
public static class Request extends HuaweiPacket {
public Request (ParamsProvider paramsProvider, boolean accountPairingOptimization, String account) {
public Request (ParamsProvider paramsProvider, boolean accountPairingOptimization) {
super(paramsProvider);
this.serviceId = AccountRelated.id;
this.commandId = id;
this.tlv = new HuaweiTLV();
if (account.length() > 0) {
tlv.put(0x01, account);
} else {
tlv.put(0x01, (byte)0x00);
}
this.tlv = new HuaweiTLV()
.put(0x01, (byte)0x00);
if (accountPairingOptimization) {
this.tlv.put(0x03, (byte)0x01);
}

View File

@ -26,7 +26,6 @@ import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.GregorianCalendar;
@ -189,7 +188,6 @@ public class CalendarReceiver extends BroadcastReceiver {
calendarEventSpec.id = i;
calendarEventSpec.title = calendarEvent.getTitle();
calendarEventSpec.allDay = calendarEvent.isAllDay();
calendarEventSpec.reminders = new ArrayList<>(calendarEvent.getRemindersAbsoluteTs());
calendarEventSpec.timestamp = calendarEvent.getBeginSeconds();
calendarEventSpec.durationInSeconds = calendarEvent.getDurationSeconds(); //FIXME: leads to problems right now
if (calendarEvent.isAllDay()) {

View File

@ -22,30 +22,35 @@ import android.location.LocationListener;
/**
* An abstract location provider, which periodically sends a location update to the provided {@link LocationListener}.
*/
public abstract class GBLocationProvider {
private final Context context;
public abstract class AbstractLocationProvider {
private final LocationListener locationListener;
public GBLocationProvider(final Context context, final LocationListener locationListener) {
this.context = context;
public AbstractLocationProvider(final LocationListener locationListener) {
this.locationListener = locationListener;
}
public final Context getContext() {
return this.context;
}
public final LocationListener getLocationListener() {
protected final LocationListener getLocationListener() {
return this.locationListener;
}
/**
* Start sending periodic location updates.
*
* @param context the {@link Context}.
*/
public abstract void start(final int interval);
abstract void start(final Context context);
/**
* Start sending periodic location updates.
*
* @param context the {@link Context}.
*/
abstract void start(final Context context, final int interval);
/**
* Stop sending periodic location updates.
*
* @param context the {@link Context}.
*/
public abstract void stop();
abstract void stop(final Context context);
}

View File

@ -21,14 +21,10 @@ import android.location.LocationListener;
import android.os.Bundle;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
/**
* An implementation of a {@link LocationListener} that forwards the location updates to the
@ -37,18 +33,18 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBLocationListener implements LocationListener {
private static final Logger LOG = LoggerFactory.getLogger(GBLocationListener.class);
private final GBDevice device;
private final EventHandler eventHandler;
private Location previousLocation;
// divide by 3.6 to get km/h to m/s
private static final double SPEED_THRESHOLD = 1.0 / 3.6;
public GBLocationListener(final GBDevice device) {
this.device = device;
public GBLocationListener(final EventHandler eventHandler) {
this.eventHandler = eventHandler;
}
@Override
public void onLocationChanged(@NonNull final Location location) {
public void onLocationChanged(final Location location) {
LOG.info("Location changed: {}", location);
// Correct the location time
@ -65,16 +61,16 @@ public class GBLocationListener implements LocationListener {
previousLocation = location;
GBApplication.deviceService(device).onSetGpsLocation(location);
eventHandler.onSetGpsLocation(location);
}
@Override
public void onProviderDisabled(@NonNull final String provider) {
public void onProviderDisabled(final String provider) {
LOG.info("onProviderDisabled: {}", provider);
}
@Override
public void onProviderEnabled(@NonNull final String provider) {
public void onProviderEnabled(final String provider) {
LOG.info("onProviderDisabled: {}", provider);
}

View File

@ -0,0 +1,140 @@
/* Copyright (C) 2022-2024 halemmerich, José Rebelo, LukasEdl, Martin Boonk
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 <https://www.gnu.org/licenses/>. */
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.HashSet;
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 running.
*/
public class GBLocationManager {
private static final Logger LOG = LoggerFactory.getLogger(GBLocationManager.class);
/**
* The current number of running listeners.
*/
private static Map<EventHandler, Map<LocationProviderType, AbstractLocationProvider>> providers = new HashMap<>();
public static void start(final Context context, final EventHandler eventHandler) {
GBLocationManager.start(context, eventHandler, LocationProviderType.GPS, null);
}
public static void start(final Context context, final EventHandler eventHandler, final LocationProviderType providerType, Integer updateInterval) {
LOG.info("Starting");
if (providers.containsKey(eventHandler) && providers.get(eventHandler).containsKey(providerType)) {
LOG.warn("EventHandler already registered");
return;
}
GB.createGpsNotification(context, providers.size());
final GBLocationListener locationListener = new GBLocationListener(eventHandler);
final AbstractLocationProvider locationProvider;
switch (providerType) {
case GPS:
LOG.info("Using gps location provider");
locationProvider = new PhoneGpsLocationProvider(locationListener);
break;
case NETWORK:
LOG.info("Using network location provider");
locationProvider = new PhoneNetworkLocationProvider(locationListener);
break;
default:
LOG.info("Using default location provider: GPS");
locationProvider = new PhoneGpsLocationProvider(locationListener);
}
if (updateInterval != null) {
locationProvider.start(context, updateInterval);
} else {
locationProvider.start(context);
}
if (providers.containsKey(eventHandler)) {
providers.get(eventHandler).put(providerType, locationProvider);
} else {
Map<LocationProviderType, AbstractLocationProvider> providerMap = new HashMap<>();
providerMap.put(providerType, locationProvider);
providers.put(eventHandler, providerMap);
}
}
public static void stop(final Context context, final EventHandler eventHandler) {
GBLocationManager.stop(context, eventHandler, null);
}
public static void stop(final Context context, final EventHandler eventHandler, final LocationProviderType gpsType) {
if (!providers.containsKey(eventHandler)) return;
Map<LocationProviderType, AbstractLocationProvider> providerMap = providers.get(eventHandler);
if (gpsType == null) {
Set<LocationProviderType> toBeRemoved = new HashSet<>();
for (LocationProviderType providerType: providerMap.keySet()) {
stopProvider(context, providerMap.get(providerType));
toBeRemoved.add(providerType);
}
for (final LocationProviderType providerType : toBeRemoved) {
providerMap.remove(providerType);
}
} else {
stopProvider(context, providerMap.get(gpsType));
providerMap.remove(gpsType);
}
LOG.debug("Remaining providers: " + providers.size());
if (providers.get(eventHandler).size() == 0)
providers.remove(eventHandler);
updateNotification(context);
}
private static void updateNotification(final Context context){
if (!providers.isEmpty()) {
GB.createGpsNotification(context, providers.size());
} else {
GB.removeGpsNotification(context);
}
}
private static void stopProvider(final Context context, AbstractLocationProvider locationProvider) {
if (locationProvider != null) {
locationProvider.stop(context);
}
}
public static void stopAll(final Context context) {
for (EventHandler eventHandler : providers.keySet()) {
stop(context, eventHandler);
}
}
}

View File

@ -1,47 +0,0 @@
/* Copyright (C) 2022-2024 LukasEdl
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.externalevents.gps;
import android.content.Context;
import android.location.LocationManager;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.providers.MockLocationProvider;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.providers.PhoneLocationProvider;
public enum GBLocationProviderType {
GPS {
@Override
public GBLocationProvider newInstance(final Context context, final GBLocationListener locationListener) {
return new PhoneLocationProvider(context, locationListener, LocationManager.GPS_PROVIDER);
}
},
NETWORK {
@Override
public GBLocationProvider newInstance(final Context context, final GBLocationListener locationListener) {
return new PhoneLocationProvider(context, locationListener, LocationManager.NETWORK_PROVIDER);
}
},
MOCK {
@Override
public GBLocationProvider newInstance(final Context context, final GBLocationListener locationListener) {
return new MockLocationProvider(context, locationListener);
}
},
;
public abstract GBLocationProvider newInstance(final Context context, final GBLocationListener locationListener);
}

View File

@ -1,184 +0,0 @@
/* Copyright (C) 2022-2024 halemmerich, José Rebelo, LukasEdl, Martin Boonk
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.externalevents.gps;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
/**
* A static location manager, which keeps track of what providers are currently running. A notification is kept
* while there is at least one provider running.
*/
public class GBLocationService extends BroadcastReceiver {
private static final Logger LOG = LoggerFactory.getLogger(GBLocationService.class);
public static final String ACTION_START = "GBLocationService.START";
public static final String ACTION_STOP = "GBLocationService.STOP";
public static final String ACTION_STOP_ALL = "GBLocationService.STOP_ALL";
public static final String EXTRA_TYPE = "extra_type";
public static final String EXTRA_INTERVAL = "extra_interval";
private final Context context;
private final Map<GBDevice, List<GBLocationProvider>> providersByDevice = new HashMap<>();
public GBLocationService(final Context context) {
this.context = context;
}
@Override
public void onReceive(final Context context, final Intent intent) {
if (intent.getAction() == null) {
LOG.warn("Action is null");
return;
}
final GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
switch (intent.getAction()) {
case ACTION_START:
if (device == null) {
LOG.error("Device is null for {}", intent.getAction());
return;
}
final GBLocationProviderType providerType = GBLocationProviderType.valueOf(
intent.hasExtra(EXTRA_TYPE) ? intent.getStringExtra(EXTRA_TYPE) : "GPS"
);
final int updateInterval = intent.getIntExtra(EXTRA_INTERVAL, 1000);
LOG.debug("Starting location provider {} for {}", providerType, device.getAliasOrName());
if (!providersByDevice.containsKey(device)) {
providersByDevice.put(device, new ArrayList<>());
}
updateNotification();
final List<GBLocationProvider> existingProviders = providersByDevice.get(device);
final GBLocationListener locationListener = new GBLocationListener(device);
final GBLocationProvider locationProvider = providerType.newInstance(context, locationListener);
locationProvider.start(updateInterval);
Objects.requireNonNull(existingProviders).add(locationProvider);
return;
case ACTION_STOP:
if (device != null) {
stopDevice(device);
updateNotification();
} else {
stopAll();
}
return;
case ACTION_STOP_ALL:
stopAll();
return;
default:
LOG.warn("Unknown action {}", intent.getAction());
}
}
public void stopDevice(final GBDevice device) {
LOG.debug("Stopping location providers for {}", device.getAliasOrName());
final List<GBLocationProvider> providers = providersByDevice.remove(device);
if (providers != null) {
for (final GBLocationProvider provider : providers) {
provider.stop();
}
}
}
public IntentFilter buildFilter() {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_START);
intentFilter.addAction(ACTION_STOP);
return intentFilter;
}
public void stopAll() {
LOG.info("Stopping location service for all devices");
final List<GBDevice> gbDevices = new ArrayList<>(providersByDevice.keySet());
for (GBDevice d : gbDevices) {
stopDevice(d);
}
updateNotification();
}
public static void start(final Context context,
@NonNull final GBDevice device,
final GBLocationProviderType providerType,
final int updateInterval) {
final Intent intent = new Intent(ACTION_START);
intent.putExtra(GBDevice.EXTRA_DEVICE, device);
intent.putExtra(EXTRA_TYPE, providerType.name());
intent.putExtra(EXTRA_INTERVAL, updateInterval);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
public static void stop(final Context context, @Nullable final GBDevice device) {
final Intent intent = new Intent(ACTION_STOP);
intent.putExtra(GBDevice.EXTRA_DEVICE, device);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
private void updateNotification() {
if (!providersByDevice.isEmpty()) {
final Intent notificationIntent = new Intent(context, GBLocationService.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
final PendingIntent pendingIntent = PendingIntentUtils.getActivity(context, 0, notificationIntent, 0, false);
final NotificationCompat.Builder nb = new NotificationCompat.Builder(context, GB.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, providersByDevice.size()))
.setContentIntent(pendingIntent)
.setSmallIcon(R.drawable.ic_gps_location)
.setOngoing(true);
GB.notify(GB.NOTIFICATION_ID_GPS, nb.build(), context);
} else {
GB.removeNotification(GB.NOTIFICATION_ID_GPS, context);
}
}
}

View File

@ -0,0 +1,22 @@
/* Copyright (C) 2022-2024 LukasEdl
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.externalevents.gps;
public enum LocationProviderType {
GPS,
NETWORK,
}

View File

@ -14,7 +14,7 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.externalevents.gps.providers;
package nodomain.freeyourgadget.gadgetbridge.externalevents.gps;
import android.content.Context;
import android.location.Location;
@ -26,14 +26,13 @@ import android.os.SystemClock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProvider;
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 GBLocationProvider {
public class MockLocationProvider extends AbstractLocationProvider {
private static final Logger LOG = LoggerFactory.getLogger(MockLocationProvider.class);
private Location previousLocation = new CurrentPosition().getLastKnownLocation();
@ -41,12 +40,12 @@ public class MockLocationProvider extends GBLocationProvider {
/**
* Interval between location updates, in milliseconds.
*/
private static final int DEFAULT_INTERVAL = 1000;
private final int interval = 1000;
/**
* Difference between location updates, in degrees.
*/
private static final float COORD_DIFF = 0.0002f;
private final float coordDiff = 0.0002f;
/**
* Whether the handler is running.
@ -55,40 +54,50 @@ public class MockLocationProvider extends GBLocationProvider {
private final Handler handler = new Handler(Looper.getMainLooper());
public MockLocationProvider(final Context context, final LocationListener locationListener) {
super(context, locationListener);
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());
newLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
getLocationListener().onLocationChanged(newLocation);
previousLocation = newLocation;
if (running) {
handler.postDelayed(this, interval);
}
}
};
public MockLocationProvider(LocationListener locationListener) {
super(locationListener);
}
@Override
public void start(final int interval) {
void start(final Context context) {
LOG.info("Starting mock location provider");
running = true;
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (!running) {
return;
}
final Location newLocation = new Location(previousLocation);
newLocation.setLatitude(previousLocation.getLatitude() + COORD_DIFF);
newLocation.setTime(System.currentTimeMillis());
newLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
getLocationListener().onLocationChanged(newLocation);
previousLocation = newLocation;
if (running) {
handler.postDelayed(this, interval);
}
}
}, interval > 0 ? interval : DEFAULT_INTERVAL);
handler.postDelayed(locationUpdateRunnable, interval);
}
@Override
public void stop() {
void start(final Context context, int minInterval) {
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;

View File

@ -14,7 +14,7 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.externalevents.gps.providers;
package nodomain.freeyourgadget.gadgetbridge.externalevents.gps;
import android.Manifest;
import android.content.Context;
@ -27,38 +27,43 @@ import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProvider;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
/**
* A location provider that uses the phone GPS, using {@link LocationManager}.
*/
public class PhoneLocationProvider extends GBLocationProvider {
private static final Logger LOG = LoggerFactory.getLogger(PhoneLocationProvider.class);
private final String provider;
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 PhoneLocationProvider(final Context context, final LocationListener locationListener, final String provider) {
super(context, locationListener);
this.provider = provider;
public PhoneGpsLocationProvider(LocationListener locationListener) {
super(locationListener);
}
public PhoneGpsLocationProvider(LocationListener locationListener, int intervalTime) {
super(locationListener);
}
@Override
public void start(final int interval) {
void start(final Context context) {
start(context, INTERVAL_MIN_TIME);
}
@Override
void start(Context context, int interval) {
LOG.info("Starting phone gps location provider");
if (!GB.checkPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) && !GB.checkPermission(getContext(), Manifest.permission.ACCESS_COARSE_LOCATION)) {
if (!GB.checkPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) && !GB.checkPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)) {
GB.toast("Location permission not granted", Toast.LENGTH_SHORT, GB.ERROR);
return;
}
final LocationManager locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);
final LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
locationManager.removeUpdates(getLocationListener());
locationManager.requestLocationUpdates(
provider,
interval > 0 ? interval : 1_000,
LocationManager.GPS_PROVIDER,
interval,
INTERVAL_MIN_DISTANCE,
getLocationListener(),
Looper.getMainLooper()
@ -69,10 +74,10 @@ public class PhoneLocationProvider extends GBLocationProvider {
}
@Override
public void stop() {
void stop(final Context context) {
LOG.info("Stopping phone gps location provider");
final LocationManager locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);
final LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
locationManager.removeUpdates(getLocationListener());
}
}

View File

@ -0,0 +1,80 @@
/* Copyright (C) 2022-2024 Lukas, LukasEdl
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.externalevents.gps;
import android.Manifest;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Looper;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
/**
* A location provider that uses the phone GPS, using {@link LocationManager}.
*/
public class PhoneNetworkLocationProvider extends AbstractLocationProvider {
private static final Logger LOG = LoggerFactory.getLogger(PhoneNetworkLocationProvider.class);
private static final int INTERVAL_MIN_TIME = 1000;
private static final int INTERVAL_MIN_DISTANCE = 0;
public PhoneNetworkLocationProvider(LocationListener locationListener) {
super(locationListener);
}
@Override
void start(final Context context) {
start(context, INTERVAL_MIN_TIME);
}
@Override
void start(Context context, int interval) {
LOG.info("Starting phone network location provider");
if (!GB.checkPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) && !GB.checkPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)) {
GB.toast("Location permission not granted", Toast.LENGTH_SHORT, GB.ERROR);
return;
}
final LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
locationManager.removeUpdates(getLocationListener());
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
interval,
INTERVAL_MIN_DISTANCE,
getLocationListener(),
Looper.getMainLooper()
);
final Location lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
LOG.debug("Last known network location: {}", lastKnownLocation);
}
@Override
void stop(final Context context) {
LOG.info("Stopping phone network location provider");
final LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
locationManager.removeUpdates(getLocationListener());
}
}

View File

@ -1,16 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid;
public class SleepAsAndroidAction {
public static final String START_TRACKING = "com.urbandroid.sleep.watch.START_TRACKING";
public static final String STOP_TRACKING = "com.urbandroid.sleep.watch.STOP_TRACKING";
public static final String SET_PAUSE = "com.urbandroid.sleep.watch.SET_PAUSE";
public static final String SET_SUSPENDED = "com.urbandroid.sleep.watch.SET_SUSPENDED";
public static final String SET_BATCH_SIZE = "com.urbandroid.sleep.watch.SET_BATCH_SIZE";
public static final String START_ALARM = "com.urbandroid.sleep.watch.START_ALARM";
public static final String STOP_ALARM = "com.urbandroid.sleep.watch.STOP_ALARM";
public static final String UPDATE_ALARM = "com.urbandroid.sleep.watch.UPDATE_ALARM";
public static final String SHOW_NOTIFICATION = "com.urbandroid.sleep.watch.SHOW_NOTIFICATION";
public static final String HINT = "com.urbandroid.sleep.watch.HINT";
public static final String CHECK_CONNECTED = "com.urbandroid.sleep.watch.CHECK_CONNECTED";
public static final String CONFIRM_CONNECTED = "com.urbandroid.sleep.watch.CONFIRM_CONNECTED";
}

View File

@ -1,23 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
public class SleepAsAndroidReceiver extends BroadcastReceiver {
private static final Logger LOG = LoggerFactory.getLogger(SleepAsAndroidReceiver.class);
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_enable", false)) {
GBApplication.deviceService().onSleepAsAndroidAction(action, intent.getExtras());
}
}
}

View File

@ -26,7 +26,7 @@ import android.content.Intent;
import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.provider.ContactsContract;
import java.util.ArrayList;
@ -451,7 +451,6 @@ public class GBDeviceService implements DeviceService {
.putExtra(EXTRA_CALENDAREVENT_TIMESTAMP, calendarEventSpec.timestamp)
.putExtra(EXTRA_CALENDAREVENT_DURATION, calendarEventSpec.durationInSeconds)
.putExtra(EXTRA_CALENDAREVENT_ALLDAY, calendarEventSpec.allDay)
.putExtra(EXTRA_CALENDAREVENT_REMINDERS, calendarEventSpec.reminders)
.putExtra(EXTRA_CALENDAREVENT_TITLE, calendarEventSpec.title)
.putExtra(EXTRA_CALENDAREVENT_DESCRIPTION, calendarEventSpec.description)
.putExtra(EXTRA_CALENDAREVENT_CALNAME, calendarEventSpec.calName)
@ -548,14 +547,4 @@ public class GBDeviceService implements DeviceService {
intent.putExtra(EXTRA_GPS_LOCATION, location);
invokeService(intent);
}
@Override
public void onSleepAsAndroidAction(String action, Bundle extras) {
Intent intent = createIntent().setAction(ACTION_SLEEP_AS_ANDROID);
intent.putExtra(EXTRA_SLEEP_AS_ANDROID_ACTION, action);
if (extras != null) {
intent.putExtras(extras);
}
invokeService(intent);
}
}

View File

@ -17,8 +17,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.model;
import java.util.ArrayList;
public class CalendarEventSpec {
public static final byte TYPE_UNKNOWN = 0;
public static final byte TYPE_SUNRISE = 1;
@ -34,5 +32,4 @@ public class CalendarEventSpec {
public String calName;
public int color;
public boolean allDay;
public ArrayList<Long> reminders; // unix epoch millis
}

View File

@ -79,9 +79,6 @@ public interface DeviceService extends EventHandler {
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 ACTION_SLEEP_AS_ANDROID = ".action.sleep_as_android";
String EXTRA_SLEEP_AS_ANDROID_ACTION = "sleepasandroid_action";
String EXTRA_NOTIFICATION_BODY = "notification_body";
String EXTRA_NOTIFICATION_FLAGS = "notification_flags";
String EXTRA_NOTIFICATION_ID = "notification_id";
@ -162,7 +159,6 @@ public interface DeviceService extends EventHandler {
String EXTRA_CALENDAREVENT_TIMESTAMP = "calendarevent_timestamp";
String EXTRA_CALENDAREVENT_DURATION = "calendarevent_duration";
String EXTRA_CALENDAREVENT_ALLDAY = "calendarevent_allday";
String EXTRA_CALENDAREVENT_REMINDERS = "calendarevent_reminders";
String EXTRA_CALENDAREVENT_TITLE = "calendarevent_title";
String EXTRA_CALENDAREVENT_DESCRIPTION = "calendarevent_description";
String EXTRA_CALENDAREVENT_LOCATION = "calendarevent_location";

View File

@ -51,8 +51,6 @@ import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBudsLiveDe
import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBudsProDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.forerunner245.GarminForerunner245Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.instinct2s.GarminInstinct2SCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.instinct2solar.GarminInstinct2SolarCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.instinct2soltac.GarminInstinct2SolTacCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.venu3.GarminVenu3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.vivoactive4s.GarminVivoActive4SCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.vivoactive5.GarminVivoActive5Coordinator;
@ -196,7 +194,6 @@ import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmismartbandpro.Red
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch2.RedmiWatch2Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch2lite.RedmiWatch2LiteCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch3.RedmiWatch3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch3active.RedmiWatch3ActiveCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch4.RedmiWatch4Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs1.XiaomiWatchS1Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs1active.XiaomiWatchS1ActiveCoordinator;
@ -204,6 +201,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs1pro.XiaomiWatc
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs3.XiaomiWatchS3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.redmiwatch3active.RedmiWatch3ActiveCoordinator;
/**
* For every supported device, a device type constant must exist.
@ -331,8 +329,6 @@ public enum DeviceType {
VIVOMOVE_HR(VivomoveHrCoordinator.class),
GARMIN_FORERUNNER_245(GarminForerunner245Coordinator.class),
GARMIN_INSTINCT_2S(GarminInstinct2SCoordinator.class),
GARMIN_INSTINCT_2_SOLAR(GarminInstinct2SolarCoordinator.class),
GARMIN_INSTINCT_2_SOLTAC(GarminInstinct2SolTacCoordinator.class),
GARMIN_VIVOMOVE_STYLE(GarminVivomoveStyleCoordinator.class),
GARMIN_VENU_3(GarminVenu3Coordinator.class),
GARMIN_VIVOACTIVE_4S(GarminVivoActive4SCoordinator.class),

View File

@ -32,7 +32,6 @@ import android.graphics.BitmapFactory;
import android.location.Location;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.text.TextUtils;
@ -1182,9 +1181,4 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
public void onSetNavigationInfo(NavigationInfoSpec navigationInfoSpec) {
}
@Override
public void onSleepAsAndroidAction(String action, Bundle extras) {
}
}

View File

@ -82,8 +82,6 @@ import nodomain.freeyourgadget.gadgetbridge.externalevents.SMSReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.SilentModeReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.TinyWeatherForecastGermanyReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
import nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid.SleepAsAndroidReceiver;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
@ -140,15 +138,13 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
}
}
private static class FeatureSet {
private class FeatureSet{
private boolean supportsWeather = false;
private boolean supportsActivityDataFetching = false;
private boolean supportsCalendarEvents = false;
private boolean supportsMusicInfo = false;
private boolean supportsNavigation = false;
private boolean supportsSleepAsAndroid = false;
public boolean supportsWeather() {
return supportsWeather;
}
@ -189,12 +185,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
this.supportsNavigation = supportsNavigation;
}
public boolean supportsSleepAsAndroid() { return supportsSleepAsAndroid; }
public void setSupportsSleepAsAndroid(boolean supportsSleepAsAndroid) {
this.supportsSleepAsAndroid = supportsSleepAsAndroid;
}
public void logicalOr(DeviceCoordinator operand){
if(operand.supportsCalendarEvents()){
setSupportsCalendarEvents(true);
@ -211,9 +201,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
if(operand.supportsNavigation()){
setSupportsNavigation(true);
}
if (operand.supportsSleepAsAndroid()) {
setSupportsSleepAsAndroid(true);
}
}
}
@ -256,7 +243,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
private AutoConnectIntervalReceiver mAutoConnectInvervalReceiver = null;
private AlarmReceiver mAlarmReceiver = null;
private final List<CalendarReceiver> mCalendarReceiver = new ArrayList<>();
private List<CalendarReceiver> mCalendarReceiver = new ArrayList<>();
private CMWeatherReceiver mCMWeatherReceiver = null;
private LineageOsWeatherReceiver mLineageOsWeatherReceiver = null;
private TinyWeatherForecastGermanyReceiver mTinyWeatherForecastGermanyReceiver = null;
@ -264,12 +251,9 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
private OmniJawsObserver mOmniJawsObserver = null;
private final DeviceSettingsReceiver deviceSettingsReceiver = new DeviceSettingsReceiver();
private final IntentApiReceiver intentApiReceiver = new IntentApiReceiver();
private GBLocationService locationService = null;
private OsmandEventReceiver mOsmandAidlHelper = null;
private SleepAsAndroidReceiver mSleepAsAndroidReceiver = null;
private HashMap<String, Long> deviceLastScannedTimestamps = new HashMap<>();
private final String[] mMusicActions = {
@ -861,7 +845,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
calendarEventSpec.timestamp = intent.getIntExtra(EXTRA_CALENDAREVENT_TIMESTAMP, -1);
calendarEventSpec.durationInSeconds = intent.getIntExtra(EXTRA_CALENDAREVENT_DURATION, -1);
calendarEventSpec.allDay = intent.getBooleanExtra(EXTRA_CALENDAREVENT_ALLDAY, false);
calendarEventSpec.reminders = (ArrayList<Long>) intent.getSerializableExtra(EXTRA_CALENDAREVENT_REMINDERS);
calendarEventSpec.title = intent.getStringExtra(EXTRA_CALENDAREVENT_TITLE);
calendarEventSpec.description = intent.getStringExtra(EXTRA_CALENDAREVENT_DESCRIPTION);
calendarEventSpec.location = intent.getStringExtra(EXTRA_CALENDAREVENT_LOCATION);
@ -1086,13 +1069,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
final Location location = intent.getParcelableExtra(EXTRA_GPS_LOCATION);
deviceSupport.onSetGpsLocation(location);
break;
case ACTION_SLEEP_AS_ANDROID:
if(device.getDeviceCoordinator().supportsSleepAsAndroid() && GBApplication.getPrefs().getString("sleepasandroid_device", new String()).equals(device.getAddress()))
{
final String sleepAsAndroidAction = intent.getStringExtra(EXTRA_SLEEP_AS_ANDROID_ACTION);
deviceSupport.onSleepAsAndroidAction(sleepAsAndroidAction, intent.getExtras());
}
break;
}
}
@ -1344,11 +1320,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
registerReceiver(mSilentModeReceiver, filter);
}
if (locationService == null) {
locationService = new GBLocationService(this);
LocalBroadcastManager.getInstance(this).registerReceiver(locationService, locationService.buildFilter());
}
if (mOsmandAidlHelper == null && features.supportsNavigation()) {
mOsmandAidlHelper = new OsmandEventReceiver(this.getApplication());
}
@ -1384,14 +1355,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
}
}
if (features.supportsSleepAsAndroid())
{
if (mSleepAsAndroidReceiver == null) {
mSleepAsAndroidReceiver = new SleepAsAndroidReceiver();
registerReceiver(mSleepAsAndroidReceiver, new IntentFilter());
}
}
if (GBApplication.getPrefs().getBoolean("auto_fetch_enabled", false) &&
features.supportsActivityDataFetching() && mGBAutoFetchReceiver == null) {
mGBAutoFetchReceiver = new GBAutoFetchReceiver();
@ -1431,11 +1394,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
unregisterReceiver(mSilentModeReceiver);
mSilentModeReceiver = null;
}
if (locationService != null) {
LocalBroadcastManager.getInstance(this).unregisterReceiver(locationService);
locationService.stopAll();
locationService = null;
}
if (mCMWeatherReceiver != null) {
unregisterReceiver(mCMWeatherReceiver);
mCMWeatherReceiver = null;
@ -1464,10 +1422,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
unregisterReceiver(mGenericWeatherReceiver);
mGenericWeatherReceiver = null;
}
if (mSleepAsAndroidReceiver != null) {
unregisterReceiver(mSleepAsAndroidReceiver);
mSleepAsAndroidReceiver = null;
}
}
}

View File

@ -22,12 +22,12 @@ import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.UUID;
@ -512,12 +512,4 @@ public class ServiceDeviceSupport implements DeviceSupport {
}
delegate.onSetGpsLocation(location);
}
@Override
public void onSleepAsAndroidAction(String action, Bundle extras) {
if (checkBusy("sleep as android")) {
return;
}
delegate.onSleepAsAndroidAction(action, extras);
}
}

View File

@ -1,573 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.service;
import android.content.Context;
import android.content.Intent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.SleepAsAndroidFeature;
import nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid.SleepAsAndroidAction;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class SleepAsAndroidSender {
private final Logger LOG = LoggerFactory.getLogger(SleepAsAndroidSender.class);
private final String PACKAGE_SLEEP_AS_ANDROID = "com.urbandroid.sleep";
private final String ACTION_EXTRA_DATA_UPDATE = "com.urbandroid.sleep.ACTION_EXTRA_DATA_UPDATE";
private final String ACTION_MOVEMENT_DATA_UPDATE = "com.urbandroid.sleep.watch.DATA_UPDATE";
private final String ACTION_HEART_RATE_DATA_UPDATE = "com.urbandroid.sleep.watch.HR_DATA_UPDATE";
private final String ACTION_RESUME_FROM_WATCH = "com.urbandroid.sleep.watch.RESUME_FROM_WATCH";
private final String ACTION_PAUSE_FROM_WATCH = "com.urbandroid.sleep.watch.PAUSE_FROM_WATCH";
private final String ACTION_SNOOZE_FROM_WATCH = "com.urbandroid.sleep.watch.SNOOZE_FROM_WATCH";
private final String ACTION_DISMISS_FROM_WATCH = "com.urbandroid.sleep.watch.DISMISS_FROM_WATCH";
private final String MAX_RAW_DATA = "MAX_RAW_DATA";
private final String DATA = "DATA";
private final String EXTRA_DATA_HR = "com.urbandroid.sleep.EXTRA_DATA_HR";
private final String EXTRA_DATA_RR = "com.urbandroid.sleep.EXTRA_DATA_RR";
private final String EXTRA_DATA_SPO2 = "com.urbandroid.sleep.EXTRA_DATA_SPO2";
private final String EXTRA_DATA_SDNN = "com.urbandroid.sleep.EXTRA_DATA_SDNN";
private final String EXTRA_DATA_TIMESTAMP = "com.urbandroid.sleep.EXTRA_DATA_TIMESTAMP";
private final String EXTRA_DATA_FRAMERATE = "com.urbandroid.sleep.EXTRA_DATA_FRAMERATE";
private final String EXTRA_DATA_BATCH = "com.urbandroid.sleep.EXTRA_DATA_BATCH";
private GBDevice device;
private boolean trackingOngoing = false;
private boolean trackingPaused = false;
private ScheduledExecutorService trackingPauseScheduler;
private long batchSize = 1;
private long lastRawDataMs = 0;
private float maxRawData = 0;
private long lastHrDataMs = 0;
private ArrayList<Float> hrData = new ArrayList<>();
private ArrayList<Float> accData = new ArrayList<>();
private ScheduledExecutorService accDataScheduler;
private Set<SleepAsAndroidFeature> features;
public SleepAsAndroidSender(GBDevice gbDevice) {
this.device = gbDevice;
this.features = gbDevice.getDeviceCoordinator().getSleepAsAndroidFeatures();
}
/**
* Check if a SleepAsAndroid feature is enabled
*
* @param feature the feature
* @return true if the feature is enabled
*/
public boolean hasFeature(SleepAsAndroidFeature feature) {
return this.features.contains(feature);
}
/**
* Check if a SleepAsAndroid feature is enabled
* @param feature
* @return
*/
public boolean isFeatureEnabled(SleepAsAndroidFeature feature) {
boolean enabled = isSleepAsAndroidEnabled();
if (enabled) {
switch (feature) {
case ACCELEROMETER:
enabled = GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_feat_movement", false);
break;
case HEART_RATE:
enabled = GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_feat_hr", false);
break;
case SPO2:
enabled = GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_feat_spo2", false);
break;
case OXIMETRY:
enabled = GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_feat_oximetry", false);
break;
case NOTIFICATIONS:
enabled = GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_feat_notifications", false);
break;
case ALARMS:
enabled = GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_feat_alarms", false);
break;
default:
break;
}
}
return enabled;
}
/**
* Get all enabled features
*
* @return all enabled features
*/
public Set<SleepAsAndroidFeature> getFeatures() {
return features;
}
/**
* Start tracking
*/
public void startTracking() {
if (!isDeviceDefault()) return;
stopTracking();
accDataScheduler = Executors.newSingleThreadScheduledExecutor();
accDataScheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
aggregateAndSendAccelData();
}
}, 9999, 9999, TimeUnit.MILLISECONDS);
lastRawDataMs = System.currentTimeMillis();
lastHrDataMs = System.currentTimeMillis();
this.trackingOngoing = true;
}
/**
* Stop tracking
*/
public void stopTracking() {
if (!isDeviceDefault() || !trackingOngoing) return;
if (accDataScheduler != null) {
accDataScheduler.shutdownNow();
accDataScheduler = null;
}
this.trackingOngoing = false;
this.hrData = new ArrayList<>();
this.accData = new ArrayList<>();
this.lastHrDataMs = 0;
this.lastRawDataMs = 0;
}
/**
* Pause tracking
*
* @param timeout the timeout in milliseconds before resuming
*/
public void pauseTracking(long timeout) {
if (!isDeviceDefault() || !trackingOngoing) return;
if (timeout <= 0) {
resumeTracking();
return;
}
pauseTracking();
trackingPauseScheduler = setPauseTracking(timeout);
}
/**
* Same as {@link #pauseTracking(long)} but pausing and resuming is controlled by a toggle
*
* @param suspended true if the tracking should be paused, false if should be resumed
*/
public void pauseTracking(boolean suspended) {
if (!isDeviceDefault() || !trackingOngoing) return;
trackingPaused = suspended;
if (!trackingPaused) {
resumeTracking();
return;
}
}
/**
* Set a scheduler to resume tracking
*
* @param delay the delay
* @return the scheduler
*/
private ScheduledExecutorService setPauseTracking(long delay) {
trackingPaused = true;
trackingPauseScheduler = Executors.newSingleThreadScheduledExecutor();
trackingPauseScheduler.schedule(new Runnable() {
@Override
public void run() {
resumeTracking();
}
}, delay, TimeUnit.MILLISECONDS);
return trackingPauseScheduler;
}
/**
* Pause tracking
*/
private void pauseTracking() {
if (!isDeviceDefault() && trackingPaused) return;
trackingPaused = true;
stopTracking();
}
/**
* Resume tracking
*/
private void resumeTracking() {
if (!isDeviceDefault() && !trackingPaused) return;
if (trackingPauseScheduler != null) {
trackingPauseScheduler.shutdownNow();
trackingPauseScheduler = null;
}
trackingPaused = false;
startTracking();
}
/**
* Set the batch size
*
* @param batchSize the batch size
*/
public void setBatchSize(long batchSize) {
if (!isDeviceDefault()) return;
LOG.debug("Setting batch size to " + batchSize);
this.batchSize = batchSize;
}
/**
* Confirm that the device is connected
*/
public void confirmConnected() {
if (!isDeviceDefault()) return;
LOG.debug("Confirming connected");
Intent intent = new Intent(SleepAsAndroidAction.CONFIRM_CONNECTED);
broadcastToSleepAsAndroid(intent);
}
/**
* On accelerometer changed
*
* @param x the x value
* @param y the y value
* @param z the z value
*/
public void onAccelChanged(float x, float y, float z) {
if (!isDeviceDefault() || !isFeatureEnabled(SleepAsAndroidFeature.ACCELEROMETER) || !hasFeature(SleepAsAndroidFeature.ACCELEROMETER) || !trackingOngoing)
return;
if (trackingPaused)
return;
updateMaxRawData(x, y, z);
}
/**
* Aggregate and send the acceleration data
*/
private synchronized void aggregateAndSendAccelData() {
if (!trackingOngoing || trackingPaused) return;
if (maxRawData > 0) {
accData.add(maxRawData);
maxRawData = 0;
if (accData.size() == batchSize) {
sendAccelData();
}
}
}
/**
* Send the acceleration data
*/
private void sendAccelData() {
LOG.debug("Sending movement data: " + this.accData + " batch size: " + batchSize + " array size: " + accData.size());
Intent intent = new Intent(ACTION_MOVEMENT_DATA_UPDATE);
intent.putExtra(MAX_RAW_DATA, convertToFloatArray(this.accData));
accData.clear();
broadcastToSleepAsAndroid(intent);
}
/**
* Update the max raw data
* @param x the x value
* @param y the y value
* @param z the z value
*/
private void updateMaxRawData(float x, float y, float z) {
float maxRaw = calculateAccelerationMagnitude(x, y, z);
if (maxRaw > maxRawData) {
maxRawData = maxRaw;
}
}
/**
* Calculate the acceleration magnitude
* @param x the x value
* @param y the y value
* @param z the z value
* @return
*/
protected float calculateAccelerationMagnitude(float x, float y, float z) {
double sqrt = Math.sqrt((x * x) + (y * y) + (z * z));
return (float)sqrt;
}
/**
* On heart rate changed
*
* @param hr the heart rate
* @param sendDelay the send delay in ms. If 0 the data will be send right away. Anything bigger will gather all the data then send it all after the specified interval
*/
public void onHrChanged(float hr, long sendDelay) {
if (!isDeviceDefault() || !isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE) || !hasFeature(SleepAsAndroidFeature.HEART_RATE) || !trackingOngoing)
return;
if (trackingPaused) return;
updateLastHrData(hr);
if (lastHrDataMs == 0) {
lastHrDataMs = System.currentTimeMillis();
}
long ms = System.currentTimeMillis();
if (ms - lastHrDataMs >= sendDelay) {
lastHrDataMs = ms;
sendHrData();
}
}
/**
* Send the heart rate data
*/
private synchronized void sendHrData() {
LOG.debug("Sending heart rate data: " + this.hrData);
Intent intent = new Intent(ACTION_HEART_RATE_DATA_UPDATE);
intent.putExtra(DATA, convertToFloatArray(this.hrData));
broadcastToSleepAsAndroid(intent);
this.hrData.clear();
}
/**
* Update the last heart rate data
* @param hr the heart rate
*/
private void updateLastHrData(float hr) {
this.hrData.add(hr);
}
/**
* This is a generic intent to carry data from various sensors.
* See Sleep As Android documentation for parameters values.
* <a href="https://docs.sleep.urbandroid.org/devs/wearable_api.html#send-various-body-sensors-data-hr-rr-spo2-sdnn">...</a>
*/
public synchronized void sendExtra(Float hr, Long extraDataTimestamp, Long extraDataFramerate, ArrayList<Float> extraDataRR, ArrayList<Float> spo2Batch, Float sdnn, ArrayList<Float> extraDataBatch) {
if (!isDeviceDefault() || !trackingOngoing) return;
if (trackingPaused) return;
Context context = GBApplication.getContext();
Intent intent = new Intent(ACTION_EXTRA_DATA_UPDATE);
// Heart Rate
if (hr != null && (hasFeature(SleepAsAndroidFeature.HEART_RATE) && isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE))) {
intent.putExtra(EXTRA_DATA_HR, hr);
}
// SpO2
if (spo2Batch != null && (hasFeature(SleepAsAndroidFeature.SPO2) && isFeatureEnabled(SleepAsAndroidFeature.SPO2))) {
intent.putExtra(EXTRA_DATA_SPO2, true);
intent.putExtra(EXTRA_DATA_BATCH, convertToFloatArray(spo2Batch));
}
// SDNN
if (sdnn != null && (hasFeature(SleepAsAndroidFeature.HEART_RATE) && isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE))) {
intent.putExtra(EXTRA_DATA_SDNN, sdnn);
}
// RR Intervals
if (extraDataRR != null && (hasFeature(SleepAsAndroidFeature.HEART_RATE) && isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE))) {
intent.putExtra(EXTRA_DATA_RR, convertToFloatArray(extraDataRR));
}
if (extraDataBatch != null) {
if (isDeviceDefault()) {
for (int i = 0; i < extraDataBatch.size(); i++) {
extraDataBatch.set(i, 0.0f);
}
}
intent.putExtra(EXTRA_DATA_BATCH, convertToFloatArray(extraDataBatch));
}
if (extraDataTimestamp != null) {
intent.putExtra(EXTRA_DATA_TIMESTAMP, extraDataTimestamp);
}
if (extraDataFramerate != null) {
intent.putExtra(EXTRA_DATA_FRAMERATE, extraDataFramerate);
}
context.sendBroadcast(intent);
}
/**
* This is a generic intent to carry data from various sensors.
* See Sleep As Android documentation for parameters values.
* <a href="https://docs.sleep.urbandroid.org/devs/wearable_api.html#send-various-body-sensors-data-hr-rr-spo2-sdnn">...</a>
*/
public void sendExtra(Float hr, Float extraDataRR, Float spo2, Float sdnn, Long sdnnTimestamp) {
if (!isDeviceDefault() || !trackingOngoing) return;
if (trackingPaused) return;
Intent intent = new Intent(ACTION_EXTRA_DATA_UPDATE);
// Heart Rate
if (hr != null && (hasFeature(SleepAsAndroidFeature.HEART_RATE) && isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE))) {
intent.putExtra(EXTRA_DATA_HR, hr);
}
// SpO2
if (spo2 != null && (hasFeature(SleepAsAndroidFeature.SPO2) && isFeatureEnabled(SleepAsAndroidFeature.SPO2))) {
intent.putExtra(EXTRA_DATA_SPO2, spo2);
}
// SDNN
if (sdnn != null && (hasFeature(SleepAsAndroidFeature.HEART_RATE) && isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE))) {
intent.putExtra(EXTRA_DATA_SDNN, sdnn);
}
if (extraDataRR != null && (hasFeature(SleepAsAndroidFeature.HEART_RATE) && isFeatureEnabled(SleepAsAndroidFeature.HEART_RATE))) {
intent.putExtra(EXTRA_DATA_RR, extraDataRR);
}
if (sdnnTimestamp != null) {
intent.putExtra(EXTRA_DATA_TIMESTAMP, sdnnTimestamp);
}
broadcastToSleepAsAndroid(intent);
}
/**
* Adds 5 minutes of tracking pause time
*/
public void sendPauseTracking() {
if (!isDeviceDefault() || !trackingOngoing) return;
LOG.debug("Sending pause");
Intent intent = new Intent(ACTION_PAUSE_FROM_WATCH);
broadcastToSleepAsAndroid(intent);
}
/**
* Resume tracking
*/
public void sendResumeTracking() {
if (!isDeviceDefault() || !trackingOngoing) return;
LOG.debug("Sending resume");
Intent intent = new Intent(ACTION_RESUME_FROM_WATCH);
broadcastToSleepAsAndroid(intent);
}
/**
* Snooze the current alarm
*/
public void sendSnoozeAlarm() {
if (!isDeviceDefault()) return;
if (!hasFeature(SleepAsAndroidFeature.ALARMS) || !isFeatureEnabled(SleepAsAndroidFeature.ALARMS)) return;
LOG.debug("Sending snooze");
Intent intent = new Intent(ACTION_SNOOZE_FROM_WATCH);
broadcastToSleepAsAndroid(intent);
}
/**
* Dismiss the current alarm
*/
public void sendDismissAlarm() {
if (!isDeviceDefault()) return;
if (!hasFeature(SleepAsAndroidFeature.ALARMS) || !isFeatureEnabled(SleepAsAndroidFeature.ALARMS)) return;
LOG.debug("Sending dismiss");
Intent intent = new Intent(ACTION_DISMISS_FROM_WATCH);
broadcastToSleepAsAndroid(intent);
}
private void broadcastToSleepAsAndroid(Intent intent) {
if (!isDeviceDefault()) return;
intent.setPackage(PACKAGE_SLEEP_AS_ANDROID);
GBApplication.getContext().sendBroadcast(intent);
}
/**
* Check if the device is set as default provider for Sleep As Android
*
* @return true if the device is set as default
*/
public boolean isDeviceDefault() {
if (device == null || !device.isInitialized()) return false;
if (isSleepAsAndroidEnabled()) {
return device.getAddress().equals(GBApplication.getPrefs().getString("sleepasandroid_device", ""));
}
return false;
}
public boolean isSleepAsAndroidEnabled() {
return GBApplication.getPrefs().getBoolean("pref_key_sleepasandroid_enable", false);
}
/**
* Validate if the device is allowed to receive a specific action
* @param action the action send my the broadcast receiver
*/
public void validateAction(String action) {
if (isDeviceDefault()) {
switch (action) {
case SleepAsAndroidAction.HINT:
case SleepAsAndroidAction.SHOW_NOTIFICATION:
if (!hasFeature(SleepAsAndroidFeature.NOTIFICATIONS) || !isFeatureEnabled(SleepAsAndroidFeature.NOTIFICATIONS)) {
throw new UnsupportedOperationException("Action not valid");
}
break;
case SleepAsAndroidAction.START_ALARM:
case SleepAsAndroidAction.STOP_ALARM:
case SleepAsAndroidAction.UPDATE_ALARM:
if (!hasFeature(SleepAsAndroidFeature.ALARMS) || !isFeatureEnabled(SleepAsAndroidFeature.ALARMS)) {
throw new UnsupportedOperationException("Action not valid");
}
break;
case SleepAsAndroidAction.START_TRACKING:
case SleepAsAndroidAction.STOP_TRACKING:
case SleepAsAndroidAction.SET_BATCH_SIZE:
if (!hasFeature(SleepAsAndroidFeature.ACCELEROMETER) || !isFeatureEnabled(SleepAsAndroidFeature.ACCELEROMETER)) {
throw new UnsupportedOperationException("Action not valid");
}
break;
default:
break;
}
} else {
throw new UnsupportedOperationException("Action not valid");
}
}
/**
* Convert an ArrayList to a float array
*
* @param list the ArrayList
* @return the float array
*/
private float[] convertToFloatArray(ArrayList<Float> list) {
float[] result = new float[list.size()];
int i = 0;
for (float f : list) {
result[i++] = f;
}
return result;
}
/**
* Get configured alarm slot
*
* @return the alarm slot to be used for setting alarms on the watch
*/
public static int getAlarmSlot() {
Prefs prefs = GBApplication.getPrefs();
String slotString = prefs.getString("sleepasandroid_alarm_slot", "");
if (!slotString.isEmpty()) {
return Integer.parseInt(slotString);
}
return 0;
}
}

View File

@ -120,8 +120,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncState;
import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncStateDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProviderType;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.LocationProviderType;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
@ -215,7 +215,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
if (!gpsUpdateSetup)
return;
LOG.info("Stop location updates");
GBLocationService.stop(getContext(), getDevice());
GBLocationManager.stop(getContext(), this);
gpsUpdateSetup = false;
}
@ -1140,14 +1140,14 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
LOG.info("Using combined GPS and NETWORK based location: " + onlyUseNetworkGPS);
if (!onlyUseNetworkGPS) {
try {
GBLocationService.start(getContext(), getDevice(), GBLocationProviderType.GPS, intervalLength);
GBLocationManager.start(getContext(), this, LocationProviderType.GPS, intervalLength);
} catch (IllegalArgumentException e) {
LOG.warn("GPS provider could not be started", e);
}
}
try {
GBLocationService.start(getContext(), getDevice(), GBLocationProviderType.NETWORK, intervalLength);
GBLocationManager.start(getContext(), this, LocationProviderType.NETWORK, intervalLength);
} catch (IllegalArgumentException e) {
LOG.warn("NETWORK provider could not be started", e);
}

View File

@ -2,15 +2,11 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.location.Location;
import android.net.Uri;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
@ -28,10 +24,8 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminAgpsInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
@ -40,7 +34,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiCore;
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiDeviceStatus;
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiFindMyWatch;
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiSettingsService;
@ -67,14 +60,12 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SetD
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SetFileFlagsMessage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SupportedFileTypesMessage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.SystemEventMessage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.NotificationSubscriptionStatusMessage;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALLOW_HIGH_MTU;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_GARMIN_DEFAULT_REPLY_SUFFIX;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SEND_APP_NOTIFICATIONS;
public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommunicator.Callback {
@ -105,10 +96,8 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
@Override
public void dispose() {
LOG.info("Garmin dispose()");
GBLocationService.stop(getContext(), getDevice());
stopMusicTimer();
super.dispose();
stopMusicTimer();
}
private void stopMusicTimer() {
@ -241,39 +230,20 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
} else if (deviceEvent instanceof NotificationSubscriptionDeviceEvent) {
final boolean enable = ((NotificationSubscriptionDeviceEvent) deviceEvent).enable;
notificationsHandler.setEnabled(enable);
final NotificationSubscriptionStatusMessage.NotificationStatus finalStatus;
if (getDevicePrefs().getBoolean(PREF_SEND_APP_NOTIFICATIONS, true)) {
finalStatus = NotificationSubscriptionStatusMessage.NotificationStatus.ENABLED;
} else {
finalStatus = NotificationSubscriptionStatusMessage.NotificationStatus.DISABLED;
}
LOG.info("NOTIFICATIONS ARE NOW enabled={}, status={}", enable, finalStatus);
sendOutgoingMessage(new NotificationSubscriptionStatusMessage(
GFDIMessage.Status.ACK,
finalStatus,
enable,
0
));
LOG.info("NOTIFICATIONS ARE NOW {}", enable ? "ON" : "OFF");
} else if (deviceEvent instanceof SupportedFileTypesDeviceEvent) {
this.supportedFileTypeList.clear();
this.supportedFileTypeList.addAll(((SupportedFileTypesDeviceEvent) deviceEvent).getSupportedFileTypes());
} else if (deviceEvent instanceof FileDownloadedDeviceEvent) {
LOG.debug("FILE DOWNLOAD COMPLETE {}", ((FileDownloadedDeviceEvent) deviceEvent).directoryEntry.getFileName());
if (!getKeepActivityDataOnDevice()) // delete file from watch upon successful download
if (false) // delete file from watch upon successful download TODO: add device setting
sendOutgoingMessage(new SetFileFlagsMessage(((FileDownloadedDeviceEvent) deviceEvent).directoryEntry.getFileIndex(), SetFileFlagsMessage.FileFlags.ARCHIVE));
}
super.evaluateGBDeviceEvent(deviceEvent);
}
private boolean getKeepActivityDataOnDevice() {
return getDevicePrefs().getBoolean("keep_activity_data_on_device", true); // TODO: change to default false once we are sure of the consequences
}
@Override
public void onFetchRecordedData(final int dataTypes) {
if (this.supportedFileTypeList.isEmpty()) {
@ -287,7 +257,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
}
@Override
public void onNotification(final NotificationSpec notificationSpec) {
public void onNotification(NotificationSpec notificationSpec) {
sendOutgoingMessage(notificationsHandler.onNotification(notificationSpec));
}
@ -432,15 +402,8 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
@Override
public void onSendConfiguration(String config) {
switch (config) {
case PREF_GARMIN_DEFAULT_REPLY_SUFFIX:
sendOutgoingMessage(toggleDefaultReplySuffix(getDevicePrefs().getBoolean(PREF_GARMIN_DEFAULT_REPLY_SUFFIX, true)));
break;
case PREF_SEND_APP_NOTIFICATIONS:
NotificationSubscriptionDeviceEvent notificationSubscriptionDeviceEvent = new NotificationSubscriptionDeviceEvent();
notificationSubscriptionDeviceEvent.enable = true; // actual status is fetched from preferences
evaluateGBDeviceEvent(notificationSubscriptionDeviceEvent);
break;
if (PREF_GARMIN_DEFAULT_REPLY_SUFFIX.equals(config)) {
sendOutgoingMessage(toggleDefaultReplySuffix(getDevicePrefs().getBoolean(PREF_GARMIN_DEFAULT_REPLY_SUFFIX, true)));
}
}
@ -456,7 +419,7 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
FileTransferHandler.DirectoryEntry directoryEntry = filesToDownload.remove();
while (checkFileExists(directoryEntry.getFileName())) {
LOG.debug("File: {} already downloaded, not downloading again.", directoryEntry.getFileName());
if (!getKeepActivityDataOnDevice()) // delete file from watch if already downloaded
if (false) // delete file from watch if already downloaded TODO: add device setting
sendOutgoingMessage(new SetFileFlagsMessage(directoryEntry.getFileIndex(), SetFileFlagsMessage.FileFlags.ARCHIVE));
directoryEntry = filesToDownload.remove();
}
@ -583,25 +546,6 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
}
}
@Override
public void onInstallApp(final Uri uri) {
final GarminAgpsInstallHandler agpsHandler = new GarminAgpsInstallHandler(uri, getContext());
if (agpsHandler.isValid()) {
try {
// Write the AGPS update to a temporary file in cache, so we can load it when requested
final File agpsFile = getAgpsFile();
try (FileOutputStream outputStream = new FileOutputStream(agpsFile)) {
outputStream.write(agpsHandler.getFile().getBytes());
LOG.info("AGPS file successfully written to the cache directory.");
} catch (final IOException e) {
LOG.error("Failed to write AGPS bytes to temporary directory", e);
}
} catch (final Exception e) {
GB.toast(getContext(), "AGPS install error: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
}
}
}
private boolean checkFileExists(String fileName) {
File dir;
try {
@ -626,34 +570,5 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
return dir;
}
@Override
public void onSetGpsLocation(final Location location) {
final GdiCore.CoreService.LocationUpdatedNotification.Builder locationUpdatedNotification = GdiCore.CoreService.LocationUpdatedNotification.newBuilder()
.addLocationData(
GarminUtils.toLocationData(location, GdiCore.CoreService.DataType.REALTIME_TRACKING)
);
final ProtobufMessage locationUpdatedNotificationRequest = protocolBufferHandler.prepareProtobufRequest(
GdiSmartProto.Smart.newBuilder().setCoreService(
GdiCore.CoreService.newBuilder().setLocationUpdatedNotification(locationUpdatedNotification)
).build()
);
sendOutgoingMessage(locationUpdatedNotificationRequest);
}
public File getAgpsFile() throws IOException {
return new File(getAgpsCacheDirectory(), "CPE.BIN");
}
private File getAgpsCacheDirectory() throws IOException {
final File cacheDir = getContext().getCacheDir();
final File agpsCacheDir = new File(cacheDir, "garmin-agps");
if (agpsCacheDir.mkdir()) {
LOG.info("AGPS cache directory for Garmin devices successfully created.");
} else if (!agpsCacheDir.exists() || !agpsCacheDir.isDirectory()) {
throw new IOException("Cannot create/locate AGPS directory for Garmin devices.");
}
return agpsCacheDir;
}
}

View File

@ -1,35 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
import android.location.Location;
import android.os.Build;
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiCore;
public final class GarminUtils {
private GarminUtils() {
// utility class
}
public static GdiCore.CoreService.LocationData toLocationData(final Location location, final GdiCore.CoreService.DataType dataType) {
final GdiCore.CoreService.LatLon positionForWatch = GdiCore.CoreService.LatLon.newBuilder()
.setLat((int) ((location.getLatitude() * 2.147483648E9d) / 180.0d))
.setLon((int) ((location.getLongitude() * 2.147483648E9d) / 180.0d))
.build();
float vAccuracy = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vAccuracy = location.getVerticalAccuracyMeters();
}
return GdiCore.CoreService.LocationData.newBuilder()
.setPosition(positionForWatch)
.setAltitude((float) location.getAltitude())
.setTimestamp(GarminTimeUtils.javaMillisToGarminTimestamp(location.getTime()))
.setHAccuracy(location.getAccuracy())
.setVAccuracy(vAccuracy)
.setPositionType(dataType)
.setBearing(location.getBearing())
.setSpeed(location.getSpeed())
.build();
}
}

View File

@ -55,7 +55,7 @@ public class NotificationsHandler implements MessageHandler {
final byte[] bytes = entry.getKey().getNotificationSpecAttribute(notificationSpec, entry.getValue());
messageWriter.writeShort(bytes.length);
messageWriter.writeBytes(bytes);
// LOG.info("ATTRIBUTE:{} value:{}/{} length:{}", entry.getKey(), new String(bytes), GB.hexdump(bytes), bytes.length);
// LOG.info("ATTRIBUTE:{} value:{} length:{}", entry.getKey(), new String(bytes), bytes.length);
}
@ -183,7 +183,7 @@ public class NotificationsHandler implements MessageHandler {
case REPLY_MESSAGES:
deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.REPLY;
deviceEvtNotificationControl.reply = message.getActionString();
if (notificationSpec.type.equals(NotificationType.GENERIC_PHONE) || notificationSpec.type.equals(NotificationType.GENERIC_SMS)) {
if (notificationSpec.type.equals(NotificationType.GENERIC_PHONE)) {
deviceEvtNotificationControl.phoneNumber = notificationSpec.phoneNumber;
} else {
deviceEvtNotificationControl.handle = mNotificationReplyAction.lookup(notificationSpec.getId()); //handle of wearable action is needed
@ -267,7 +267,7 @@ public class NotificationsHandler implements MessageHandler {
MESSAGE_SIZE(4),
DATE(5),
// POSITIVE_ACTION_LABEL(6), //needed only for legacy notification actions
NEGATIVE_ACTION_LABEL(7), //needed only for legacy notification actions
// NEGATIVE_ACTION_LABEL(7), //needed only for legacy notification actions
// Garmin extensions
// PHONE_NUMBER(126, true),
ACTIONS(127, false, true),
@ -312,10 +312,7 @@ public class NotificationsHandler implements MessageHandler {
toReturn = NOTIFICATION_DATE_FORMAT.format(new Date(notificationTimestamp));
break;
case TITLE:
if (NotificationType.GENERIC_SMS.equals(notificationSpec.type))
toReturn = notificationSpec.sender == null ? "" : notificationSpec.sender;
else
toReturn = notificationSpec.title == null ? "" : notificationSpec.title;
toReturn = notificationSpec.title == null ? "" : notificationSpec.title;
break;
case SUBTITLE:
toReturn = notificationSpec.subject == null ? "" : notificationSpec.subject;
@ -350,7 +347,6 @@ public class NotificationsHandler implements MessageHandler {
for (NotificationSpec.Action action : notificationSpec.attachedActions) {
switch (action.type) {
case NotificationSpec.Action.TYPE_WEARABLE_REPLY:
case NotificationSpec.Action.TYPE_SYNTECTIC_REPLY_PHONENR:
garminActions.add(encodeNotificationAction(NotificationAction.REPLY_MESSAGES, action.title));
break;
case NotificationSpec.Action.TYPE_SYNTECTIC_DISMISS:

View File

@ -1,10 +1,9 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin;
import android.location.Location;
import com.google.protobuf.InvalidProtocolBufferException;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -13,14 +12,8 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProviderType;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiCalendarService;
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiCore;
@ -35,7 +28,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.HttpHand
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.ProtobufMessage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.ProtobufStatusMessage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent;
import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager;
@ -50,7 +42,7 @@ public class ProtocolBufferHandler implements MessageHandler {
private final HttpHandler httpHandler;
private final DataTransferHandler dataTransferHandler;
private final Map<GdiSmsNotification.SmsNotificationService.CannedListType, String[]> cannedListTypeMap = new HashMap<>();
private Map<GdiSmsNotification.SmsNotificationService.CannedListType, String[]> cannedListTypeMap;
public ProtocolBufferHandler(GarminSupport deviceSupport) {
this.deviceSupport = deviceSupport;
@ -88,7 +80,9 @@ public class ProtocolBufferHandler implements MessageHandler {
}
boolean processed = false;
if (smart.hasCoreService()) { //TODO: unify request and response???
return prepareProtobufResponse(processProtobufCoreRequest(smart.getCoreService()), message.getRequestId());
processed = true;
processProtobufCoreResponse(smart.getCoreService());
// return prepareProtobufResponse(processProtobufCoreRequest(smart.getCoreService()), message.getRequestId());
}
if (smart.hasCalendarService()) {
return prepareProtobufResponse(processProtobufCalendarRequest(smart.getCalendarService()), message.getRequestId());
@ -176,33 +170,16 @@ public class ProtocolBufferHandler implements MessageHandler {
LOG.debug("CalendarService Skipping event {} that is out of requested time range", mEvt.getTitle());
continue;
}
if (!calendarServiceRequest.getIncludeAllDay() && mEvt.isAllDay()) {
LOG.debug("CalendarService Skipping event {} that is AllDay", mEvt.getTitle());
continue;
}
if (watchEvents.size() >= calendarServiceRequest.getMaxEvents() * 2) { //NOTE: Tested with values higher than double of the reported max without issues
LOG.debug("Reached the maximum number of events supported by the watch");
break;
}
final GdiCalendarService.CalendarService.CalendarEvent.Builder event = GdiCalendarService.CalendarService.CalendarEvent.newBuilder()
.setTitle(mEvt.getTitle().substring(0, Math.min(mEvt.getTitle().length(), calendarServiceRequest.getMaxTitleLength())))
watchEvents.add(GdiCalendarService.CalendarService.CalendarEvent.newBuilder()
.setTitle(mEvt.getTitle())
.setAllDay(mEvt.isAllDay())
.setStartDate(mEvt.getBeginSeconds())
.setEndDate(mEvt.getEndSeconds());
if (calendarServiceRequest.getIncludeLocation() && mEvt.getLocation() != null) {
event.setLocation(mEvt.getLocation().substring(0, Math.min(mEvt.getLocation().length(), calendarServiceRequest.getMaxLocationLength())));
}
if (calendarServiceRequest.getIncludeDescription() && mEvt.getDescription() != null) {
event.setDescription(mEvt.getDescription().substring(0, Math.min(mEvt.getDescription().length(), calendarServiceRequest.getMaxDescriptionLength())));
}
if (calendarServiceRequest.getIncludeOrganizer() && mEvt.getOrganizer() != null) {
event.setDescription(mEvt.getOrganizer().substring(0, Math.min(mEvt.getOrganizer().length(), calendarServiceRequest.getMaxOrganizerLength())));
}
watchEvents.add(event.build());
.setBegin(mEvt.getBeginSeconds())
.setEnd(mEvt.getEndSeconds())
.setLocation(StringUtils.defaultString(mEvt.getLocation()))
.setDescription(StringUtils.defaultString(mEvt.getDescription()))
.build()
);
}
LOG.debug("CalendarService Sending {} events to watch", watchEvents.size());
@ -210,7 +187,7 @@ public class ProtocolBufferHandler implements MessageHandler {
GdiCalendarService.CalendarService.newBuilder().setCalendarResponse(
GdiCalendarService.CalendarService.CalendarServiceResponse.newBuilder()
.addAllCalendarEvent(watchEvents)
.setStatus(GdiCalendarService.CalendarService.CalendarServiceResponse.ResponseStatus.OK)
.setUnknown(1)
)
).build();
}
@ -218,11 +195,19 @@ public class ProtocolBufferHandler implements MessageHandler {
return GdiSmartProto.Smart.newBuilder().setCalendarService(
GdiCalendarService.CalendarService.newBuilder().setCalendarResponse(
GdiCalendarService.CalendarService.CalendarServiceResponse.newBuilder()
.setStatus(GdiCalendarService.CalendarService.CalendarServiceResponse.ResponseStatus.UNKNOWN_RESPONSE_STATUS)
.setUnknown(0)
)
).build();
}
private void processProtobufCoreResponse(GdiCore.CoreService coreService) {
if (coreService.hasSyncResponse()) {
final GdiCore.CoreService.SyncResponse syncResponse = coreService.getSyncResponse();
LOG.info("Received sync status: {}", syncResponse.getStatus());
}
LOG.warn("Unknown CoreService response: {}", coreService);
}
private void processProtobufDeviceStatusResponse(GdiDeviceStatus.DeviceStatusService deviceStatusService) {
if (deviceStatusService.hasRemoteDeviceBatteryStatusResponse()) {
final GdiDeviceStatus.DeviceStatusService.RemoteDeviceBatteryStatusResponse batteryStatusResponse = deviceStatusService.getRemoteDeviceBatteryStatusResponse();
@ -241,90 +226,38 @@ public class ProtocolBufferHandler implements MessageHandler {
LOG.warn("Unknown DeviceStatusService response: {}", deviceStatusService);
}
private GdiSmartProto.Smart processProtobufCoreRequest(GdiCore.CoreService coreService) {
if (coreService.hasSyncResponse()) {
final GdiCore.CoreService.SyncResponse syncResponse = coreService.getSyncResponse();
LOG.info("Received sync status: {}", syncResponse.getStatus());
return null;
}
if (coreService.hasGetLocationRequest()) {
LOG.info("Got location request");
final Location location = new CurrentPosition().getLastKnownLocation();
final GdiCore.CoreService.GetLocationResponse.Builder response = GdiCore.CoreService.GetLocationResponse.newBuilder();
if (location.getLatitude() == 0 && location.getLongitude() == 0) {
response.setStatus(GdiCore.CoreService.GetLocationResponse.Status.NO_VALID_LOCATION);
} else {
response.setStatus(GdiCore.CoreService.GetLocationResponse.Status.OK)
.setLocationData(GarminUtils.toLocationData(location, GdiCore.CoreService.DataType.GENERAL_LOCATION));
}
return GdiSmartProto.Smart.newBuilder().setCoreService(
GdiCore.CoreService.newBuilder().setGetLocationResponse(response)).build();
}
if (coreService.hasLocationUpdatedSetEnabledRequest()) {
final GdiCore.CoreService.LocationUpdatedSetEnabledRequest locationUpdatedSetEnabledRequest = coreService.getLocationUpdatedSetEnabledRequest();
LOG.info("Received locationUpdatedSetEnabledRequest status: {}", locationUpdatedSetEnabledRequest.getEnabled());
GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Builder response = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.newBuilder()
.setStatus(GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Status.OK);
final boolean sendGpsPref = deviceSupport.getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_SEND_GPS_TO_BAND, false);
GdiCore.CoreService.Request realtimeRequest = null;
if (locationUpdatedSetEnabledRequest.getEnabled()) {
for (final GdiCore.CoreService.Request request : locationUpdatedSetEnabledRequest.getRequestsList()) {
final GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus requestedStatus;
if (GdiCore.CoreService.DataType.REALTIME_TRACKING.equals(request.getRequested())) {
realtimeRequest = request;
if (sendGpsPref) {
requestedStatus = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus.OK;
} else {
requestedStatus = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus.KO;
}
} else {
requestedStatus = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus.KO;
}
response.addRequests(
GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.newBuilder()
.setRequested(request.getRequested())
.setStatus(requestedStatus)
);
}
}
if (sendGpsPref) {
if (realtimeRequest != null) {
GBLocationService.start(
deviceSupport.getContext(),
deviceSupport.getDevice(),
GBLocationProviderType.GPS,
1000 // TODO from realtimeRequest
);
} else {
GBLocationService.stop(deviceSupport.getContext(), deviceSupport.getDevice());
}
}
return GdiSmartProto.Smart.newBuilder().setCoreService(
GdiCore.CoreService.newBuilder().setLocationUpdatedSetEnabledResponse(response)).build();
}
LOG.warn("Unknown CoreService request: {}", coreService);
return null;
}
// private GdiSmartProto.Smart processProtobufCoreRequest(GdiCore.CoreService coreService) {
// if (coreService.hasLocationUpdatedSetEnabledRequest()) { //TODO: enable location support in devicesupport
// LOG.debug("Location CoreService: {}", coreService);
//
// final GdiCore.CoreService.LocationUpdatedSetEnabledRequest locationUpdatedSetEnabledRequest = coreService.getLocationUpdatedSetEnabledRequest();
//
// LOG.info("Received locationUpdatedSetEnabledRequest status: {}", locationUpdatedSetEnabledRequest.getEnabled());
//
// GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Builder response = GdiCore.CoreService.LocationUpdatedSetEnabledResponse.newBuilder()
// .setStatus(GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Status.OK);
//
// //TODO: check and follow the preference in coordinator (see R.xml.devicesettings_workout_send_gps_to_band )
// if(locationUpdatedSetEnabledRequest.getEnabled()) {
// response.addRequests(GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.newBuilder()
// .setRequested(locationUpdatedSetEnabledRequest.getRequests(0).getRequested())
// .setStatus(GdiCore.CoreService.LocationUpdatedSetEnabledResponse.Requested.RequestedStatus.OK));
// }
//
// deviceSupport.processLocationUpdateRequest(locationUpdatedSetEnabledRequest.getEnabled(), locationUpdatedSetEnabledRequest.getRequestsList());
//
// return GdiSmartProto.Smart.newBuilder().setCoreService(
// GdiCore.CoreService.newBuilder().setLocationUpdatedSetEnabledResponse(response)).build();
// }
// LOG.warn("Unknown CoreService request: {}", coreService);
// return null;
// }
private GdiSmartProto.Smart processProtobufSmsNotificationMessage(GdiSmsNotification.SmsNotificationService smsNotificationService) {
if (smsNotificationService.hasSmsCannedListRequest()) {
LOG.debug("Got request for sms canned list");
if (null == this.cannedListTypeMap || this.cannedListTypeMap.isEmpty()) {
this.cannedListTypeMap = new HashMap<>();
// Mark canned messages as supported
deviceSupport.evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(GarminPreferences.PREF_FEAT_CANNED_MESSAGES, true));
if (this.cannedListTypeMap.isEmpty()) {
List<GdiSmsNotification.SmsNotificationService.CannedListType> requestedTypes = smsNotificationService.getSmsCannedListRequest().getRequestedTypesList();
for (GdiSmsNotification.SmsNotificationService.CannedListType type :
requestedTypes) {
@ -360,7 +293,7 @@ public class ProtocolBufferHandler implements MessageHandler {
for (GdiSmsNotification.SmsNotificationService.CannedListType requestedType : requestedTypes) {
if (this.cannedListTypeMap.containsKey(requestedType)) {
builder.addLists(GdiSmsNotification.SmsNotificationService.SmsCannedList.newBuilder()
.addAllResponse(Arrays.asList(Objects.requireNonNull(this.cannedListTypeMap.get(requestedType))))
.addAllResponse(Arrays.asList(this.cannedListTypeMap.get(requestedType)))
.setType(requestedType)
);
} else {
@ -431,6 +364,9 @@ public class ProtocolBufferHandler implements MessageHandler {
return null;
}
if (null == this.cannedListTypeMap) {
this.cannedListTypeMap = new HashMap<>();
}
this.cannedListTypeMap.put(cannedListType, cannedMessagesSpec.cannedMessages);
GdiSmartProto.Smart smart = GdiSmartProto.Smart.newBuilder()

View File

@ -1,40 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.file;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
public class GarminAgpsFile {
private static final Logger LOG = LoggerFactory.getLogger(GarminAgpsFile.class);
public static final int TAR_MAGIC_BYTES_OFFSET = 257;
public static final byte[] TAR_MAGIC_BYTES = new byte[]{
'u', 's', 't', 'a', 'r', '\0'
};
private final byte[] tarBytes;
public GarminAgpsFile(final byte[] tarBytes) {
this.tarBytes = tarBytes;
}
public boolean isValid() {
if (!ArrayUtils.equals(tarBytes, TAR_MAGIC_BYTES, TAR_MAGIC_BYTES_OFFSET)) {
LOG.debug("Is not TAR file!");
return false;
}
// TODO Add additional checks.
// Archive usually contains following files:
// CPE_GLO.BIN
// CPE_QZSS.BIN
// CPE_GPS.BIN
// CPE_GAL.BIN
return true;
}
public byte[] getBytes() {
return tarBytes.clone();
}
}

View File

@ -5,22 +5,17 @@ import com.google.protobuf.ByteString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiDataTransferService;
public class DataTransferHandler {
private static final Logger LOG = LoggerFactory.getLogger(DataTransferHandler.class);
private static final AtomicInteger idCounter = new AtomicInteger((new Random()).nextInt(Integer.MAX_VALUE / 2));
private static final AtomicInteger idCounter = new AtomicInteger(0);
private static final Map<Integer, Data> dataById = new HashMap<>();
private static final Map<Integer, ChunkInfo> unprocessedChunksByRequestId = new HashMap<>();
@ -99,13 +94,6 @@ public class DataTransferHandler {
data.onDataChunkSuccessfullyReceived(chunkInfo);
if (data.isDataSuccessfullySent()) {
LOG.info("Data successfully sent to the device (id: {}, size: {})", chunkInfo.dataId, data.data.length);
for (Callable<Void> listener : data.onDataSuccessfullySentListeners) {
try {
listener.call();
} catch (Exception e) {
LOG.error("Data listener failed.", e);
}
}
dataById.remove(chunkInfo.dataId);
} else {
LOG.debug(
@ -115,10 +103,6 @@ public class DataTransferHandler {
}
}
public static void addOnDataSuccessfullySentListener(final int dataId, final Callable<Void> listener) {
Objects.requireNonNull(dataById.get(dataId)).onDataSuccessfullySentListeners.add(listener);
}
private static class ChunkInfo {
private final int dataId;
private final int start;
@ -136,12 +120,10 @@ public class DataTransferHandler {
// Because now we have to store the whole data in RAM.
private final byte[] data;
private final TreeMap<Integer, ChunkInfo> chunksReceivedByDevice;
private final List<Callable<Void>> onDataSuccessfullySentListeners;
private Data(byte[] data) {
this.data = data;
chunksReceivedByDevice = new TreeMap<>();
onDataSuccessfullySentListeners = new ArrayList<>();
}
private byte[] getDataChunk(final int offset, final int maxChunkSize) {

View File

@ -3,15 +3,14 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.Callable;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupport;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
public class EphemerisHandler {
private static final Logger LOG = LoggerFactory.getLogger(EphemerisHandler.class);
@ -22,30 +21,21 @@ public class EphemerisHandler {
}
public byte[] handleEphemerisRequest(final String path, final Map<String, String> query) {
// TODO Return status code 304 (Not Modified) when we don't have newer data and "if-none-match" is set.
try {
final File agpsFile = deviceSupport.getAgpsFile();
if (!agpsFile.exists() || !agpsFile.isFile()) {
LOG.info("File with AGPS data does not exist.");
return null;
}
try(InputStream agpsIn = new FileInputStream(agpsFile)) {
final byte[] rawBytes = FileUtils.readAll(agpsIn, 1024 * 1024); // 1MB, they're usually ~60KB
LOG.info("Sending new AGPS data to the device.");
return rawBytes;
final File exportDirectory = deviceSupport.getWritableExportDirectory();
final File ephemerisDataFile = new File(exportDirectory, "CPE.BIN");
if (!ephemerisDataFile.exists() || !ephemerisDataFile.isFile()) {
throw new IOException("Cannot locate CPE.BIN file in export/import directory.");
}
final byte[] bytes = new byte[(int) ephemerisDataFile.length()];
final BufferedInputStream bis = new BufferedInputStream(new FileInputStream(ephemerisDataFile));
final DataInputStream dis = new DataInputStream(bis);
dis.readFully(bytes);
return bytes;
} catch (IOException e) {
LOG.error("Unable to obtain ephemeris data.", e);
return null;
}
}
public Callable<Void> getOnDataSuccessfullySentListener() {
return () -> {
LOG.info("AGPS data successfully sent to the device.");
if (deviceSupport.getAgpsFile().delete()) {
LOG.info("AGPS data was deleted from the cache folder.");
}
return null;
};
}
}

View File

@ -16,7 +16,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.zip.GZIPOutputStream;
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiHttpService;
@ -53,7 +52,6 @@ public class HttpHandler {
}
public GdiHttpService.HttpService.RawResponse handleRawRequest(final GdiHttpService.HttpService.RawRequest rawRequest) {
// TODO Return status code 304 (Not Modified) when we don't have newer data and "if-none-match" is set.
final String urlString = rawRequest.getUrl();
LOG.debug("Got rawRequest: {} - {}", rawRequest.getMethod(), urlString);
@ -76,15 +74,15 @@ public class HttpHandler {
}
final String json = GSON.toJson(weatherData);
LOG.debug("Weather response: {}", json);
return createRawResponse(rawRequest, json.getBytes(StandardCharsets.UTF_8), "application/json", null);
return createRawResponse(rawRequest, json.getBytes(StandardCharsets.UTF_8), "application/json");
} else if (path.startsWith("/ephemeris/")) {
LOG.info("Got ephemeris request for {}", path);
final byte[] ephemerisData = ephemerisHandler.handleEphemerisRequest(path, query);
byte[] ephemerisData = ephemerisHandler.handleEphemerisRequest(path, query);
if (ephemerisData == null) {
return null;
}
LOG.debug("Successfully obtained ephemeris data (length: {})", ephemerisData.length);
return createRawResponse(rawRequest, ephemerisData, "application/x-tar", ephemerisHandler.getOnDataSuccessfullySentListener());
return createRawResponse(rawRequest, ephemerisData, "application/x-tar");
} else {
LOG.warn("Unhandled path {}", urlString);
return null;
@ -94,15 +92,11 @@ public class HttpHandler {
private static GdiHttpService.HttpService.RawResponse createRawResponse(
final GdiHttpService.HttpService.RawRequest rawRequest,
final byte[] data,
final String contentType,
final Callable<Void> onDataSuccessfullySentListener
) {
final String contentType
) {
if (rawRequest.hasUseDataXfer() && rawRequest.getUseDataXfer()) {
LOG.debug("Data will be returned using data_xfer");
int id = DataTransferHandler.registerData(data);
if (onDataSuccessfullySentListener != null) {
DataTransferHandler.addOnDataSuccessfullySentListener(id, onDataSuccessfullySentListener);
}
return GdiHttpService.HttpService.RawResponse.newBuilder()
.setStatus(GdiHttpService.HttpService.Status.OK)
.setHttpStatus(200)

View File

@ -1,7 +1,7 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -77,7 +77,7 @@ public class NotificationControlMessage extends GFDIMessage {
}
private static Map<NotificationsHandler.NotificationAttribute, Integer> createGetNotificationAttributesCommand(MessageReader reader) {
final Map<NotificationsHandler.NotificationAttribute, Integer> notificationAttributesMap = new LinkedHashMap<>();
final Map<NotificationsHandler.NotificationAttribute, Integer> notificationAttributesMap = new HashMap<>();
while (reader.remaining() > 0) {
final int attributeID = reader.readByte();

View File

@ -17,8 +17,7 @@ public class NotificationSubscriptionMessage extends GFDIMessage {
this.enable = enable;
this.unk = unk;
// We do not set the status message here so we can reply with the proper notifications status
// from the device event
this.statusMessage = new NotificationSubscriptionStatusMessage(Status.ACK, NotificationSubscriptionStatusMessage.NotificationStatus.OK, enable, unk);
}
public static NotificationSubscriptionMessage parseIncoming(MessageReader reader, GarminMessage garminMessage) {

View File

@ -34,64 +34,17 @@ public class NotificationUpdateMessage extends GFDIMessage {
writer.writeByte(getCategoryValue(this.notificationType));
writer.writeByte(this.count);
writer.writeInt(this.notificationId);
writer.writeByte(getNotificationPhoneFlags());
writer.writeByte(this.useLegacyActions ? 0x00 : 0x03);
return true;
}
private int getNotificationPhoneFlags() {
EnumSet<NotificationPhoneFlags> flags = EnumSet.noneOf(NotificationPhoneFlags.class);
if (this.hasActions)
flags.add(NotificationPhoneFlags.NEW_ACTIONS);
if (this.useLegacyActions)
flags.add(NotificationPhoneFlags.LEGACY_ACTIONS);
return (int) EnumUtils.generateBitVector(NotificationPhoneFlags.class, flags);
}
//no image
//00 updatetype
// 12 flags
// 00 notif type
// 00 count
// 03000000
// 02
//image
//00
// 12
// 00
// 00
// 04000000
// 06
//0F00
// A913
// 00
// 12
// 0C
// 00
// 471D2A66
// 02
// BC14
//0F00
// A913
// 00
// 11
// 00
// 00
// 461D2A66
// 00
// 8C00
private int getCategoryFlags(NotificationType notificationType) {
EnumSet<NotificationFlag> flags = EnumSet.noneOf(NotificationFlag.class);
if (this.hasActions && this.useLegacyActions) { //only needed for legacy actions
flags.add(NotificationFlag.ACTION_ACCEPT);
flags.add(NotificationFlag.ACTION_DECLINE);
}
flags.add(NotificationFlag.ACTION_DECLINE);
switch (notificationType.getGenericType()) {
case "generic_phone":
@ -158,13 +111,5 @@ public class NotificationUpdateMessage extends GFDIMessage {
LOCATION,
ENTERTAINMENT,
SMS
}
enum NotificationPhoneFlags {
LEGACY_ACTIONS,
NEW_ACTIONS,
HAS_ATTACHMENTS,
;
}
}

View File

@ -33,8 +33,7 @@ public class NotificationSubscriptionStatusMessage extends GFDIStatusMessage {
}
public enum NotificationStatus {
ENABLED,
DISABLED
OK,
;
public static NotificationStatus fromId(int id) {

View File

@ -63,6 +63,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
@ -116,8 +117,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.GBLocationProviderType;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager;
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
@ -128,7 +128,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.model.SleepState;
import nodomain.freeyourgadget.gadgetbridge.model.WearingState;
import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.AbstractFetchOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchStatisticsOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.fetch.FetchTemperatureOperation;
@ -347,7 +346,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
private final LinkedList<AbstractFetchOperation> fetchOperationQueue = new LinkedList<>();
protected SleepAsAndroidSender sleepAsAndroidSender;
public HuamiSupport() {
this(LOG);
}
@ -374,7 +372,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
public void setContext(final GBDevice gbDevice, final BluetoothAdapter btAdapter, final Context context) {
super.setContext(gbDevice, btAdapter, context);
this.mediaManager = new MediaManager(context);
}
@Override
@ -409,9 +406,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
} else {
new InitOperation(authenticate, authFlags, cryptFlags, this, builder).perform();
}
if (sleepAsAndroidSender == null) {
sleepAsAndroidSender = new SleepAsAndroidSender(gbDevice);
}
characteristicHRControlPoint = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT);
characteristicChunked = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER);
} catch (IOException e) {
@ -2010,7 +2004,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
if (sendGpsToBand) {
lastPhoneGpsSent = 0;
sendPhoneGps(HuamiPhoneGpsStatus.SEARCHING, null);
GBLocationService.start(getContext(), getDevice(), GBLocationProviderType.GPS, 1000);
GBLocationManager.start(getContext(), this);
} else {
sendPhoneGps(HuamiPhoneGpsStatus.DISABLED, null);
}
@ -2030,7 +2024,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
protected void onWorkoutEnd() {
final boolean startOnPhone = HuamiCoordinator.getWorkoutStartOnPhone(getDevice().getAddress());
GBLocationService.stop(getContext(), getDevice());
GBLocationManager.stop(getContext(), this);
if (startOnPhone) {
LOG.info("Stopping OpenTracks recording");
@ -2618,8 +2612,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
MiBand2SampleProvider provider = new MiBand2SampleProvider(gbDevice, session);
MiBandActivitySample sample = createActivitySample(device, user, ts, provider);
sample.setHeartRate(getHeartrateBpm());
sleepAsAndroidSender.onHrChanged(sample.getHeartRate(), 0);
// sample.setSteps(getSteps());
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
sample.setRawKind(HuamiConst.TYPE_ACTIVITY); // to make it visible in the charts TODO: add a MANUAL kind for that?

View File

@ -17,7 +17,6 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos;
import static org.apache.commons.lang3.ArrayUtils.subarray;
import static java.lang.Thread.sleep;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service.*;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.SUCCESS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_NAME;
@ -42,7 +41,6 @@ import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.
import android.content.Context;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Toast;
@ -58,7 +56,6 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@ -70,8 +67,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -84,7 +79,6 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSilentMode;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service;
@ -97,7 +91,6 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid.SleepAsAndroidAction;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
@ -146,7 +139,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.service
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsPhoneService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsWatchfaceService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsWifiService;
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
@ -159,10 +151,9 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
// Tracks whether realtime HR monitoring is already started, so we can just
// send CONTINUE commands
private boolean heartRateRealtimeStarted;
private ScheduledExecutorService heartRateRealtimeScheduler;
// Keep track of whether the rawSensor is enabled
private boolean rawSensor = false;
private ScheduledExecutorService rawSensorScheduler;
// Services
private final ZeppOsServicesService servicesService = new ZeppOsServicesService(this);
@ -876,169 +867,6 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
}
}
@Override
public void onSleepAsAndroidAction(String action, Bundle extras) {
// Validate if our device can work with an action
try {
sleepAsAndroidSender.validateAction(action);
} catch (UnsupportedOperationException e) {
return;
}
// Consult the SleepAsAndroid documentation for a set of actions and their extra
// https://docs.sleep.urbandroid.org/devs/wearable_api.html
switch (action) {
case SleepAsAndroidAction.CHECK_CONNECTED:
sleepAsAndroidSender.confirmConnected();
break;
// Received when the app starts sleep tracking
case SleepAsAndroidAction.START_TRACKING:
enableRealtimeHeartRateMeasurement(true);
enableRawSensor(true);
sleepAsAndroidSender.startTracking();
break;
// Received when the app stops sleep tracking
case SleepAsAndroidAction.STOP_TRACKING:
enableRealtimeHeartRateMeasurement(false);
enableRawSensor(false);
sleepAsAndroidSender.stopTracking();
break;
// Received when the app pauses sleep tracking
// case SleepAsAndroidAction.SET_PAUSE:
// long pauseTimestamp = extras.getLong("TIMESTAMP");
// long delay = pauseTimestamp > 0 ? pauseTimestamp - System.currentTimeMillis() : 0;
// setRawSensor(delay > 0);
// enableRealtimeSamplesTimer(delay > 0);
// sleepAsAndroidSender.pauseTracking(delay);
// break;
// Same as above but controlled by a boolean value
case SleepAsAndroidAction.SET_SUSPENDED:
boolean suspended = extras.getBoolean("SUSPENDED", false);
setRawSensor(!suspended);
enableRealtimeSamplesTimer(!suspended);
sleepAsAndroidSender.pauseTracking(suspended);
// Received when the app changes the batch size for the movement data
case SleepAsAndroidAction.SET_BATCH_SIZE:
long batchSize = extras.getLong("SIZE", 12L);
sleepAsAndroidSender.setBatchSize(batchSize);
break;
// Received when the app requests the wearable to vibrate
case SleepAsAndroidAction.HINT:
int repeat = extras.getInt("REPEAT");
for (int i = 0; i < repeat; i++) {
sendFindDeviceCommand(true);
try {
sleep(500);
sendFindDeviceCommand(false);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
break;
// Received when the app sends a notificaation
case SleepAsAndroidAction.SHOW_NOTIFICATION:
NotificationSpec notificationSpec = new NotificationSpec();
notificationSpec.title = extras.getString("TITLE");
notificationSpec.body = extras.getString("BODY");
notificationService.sendNotification(notificationSpec);
break;
// Received when the app updates an alarm (Snoozing included too)
// It's better to use SleepAsAndroidAction.START_ALARM and .STOP_ALARM where possible to have more control over the alarm.
// Using .UPDATE_ALARM will let Gadgetbridge know when an alarm was set but not when it was dismissed.
case SleepAsAndroidAction.UPDATE_ALARM:
long alarmTimestamp = extras.getLong("TIMESTAMP");
// Sets the alarm at a giver hour and minute
// Snoozing from the app will create a new alarm in the future
setSleepAsAndroidAlarm(alarmTimestamp);
break;
// Received when an app alarm is stopped
case SleepAsAndroidAction.STOP_ALARM:
// Manually stop an alarm
break;
// Received when an app alarm starts
case SleepAsAndroidAction.START_ALARM:
// Manually start an alarm
break;
default:
LOG.warn("Received unsupported " + action);
break;
}
}
private void setSleepAsAndroidAlarm(long alarmTimestamp) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(new Timestamp(alarmTimestamp).getTime());
Alarm alarm = AlarmUtils.createSingleShot(SleepAsAndroidSender.getAlarmSlot(), false, false, calendar);
ArrayList<Alarm> alarms = new ArrayList<>(1);
alarms.add(alarm);
GBApplication.deviceService(gbDevice).onSetAlarms(alarms);
}
private ScheduledExecutorService startRealtimeHeartRateMeasurement() {
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
if (heartRateRealtimeStarted) {
onEnableRealtimeHeartRateMeasurement(true);
}
}
}, 0, 1000, TimeUnit.MILLISECONDS);
return service;
}
private void stopRealtimeHeartRateMeasurement() {
if (heartRateRealtimeScheduler != null) {
heartRateRealtimeScheduler.shutdown();
heartRateRealtimeScheduler = null;
}
}
private void enableRealtimeHeartRateMeasurement(boolean enable) {
onEnableRealtimeHeartRateMeasurement(enable);
if (enable) {
heartRateRealtimeScheduler = startRealtimeHeartRateMeasurement();
}
else {
stopRealtimeHeartRateMeasurement();
}
}
private void stopRawSensors() {
if (rawSensorScheduler != null) {
rawSensorScheduler.shutdown();
rawSensorScheduler = null;
}
}
private ScheduledExecutorService startRawSensors() {
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
if (rawSensor) {
setRawSensor(true);
}
}
}, 0, 10000, TimeUnit.MILLISECONDS);
return service;
}
private void enableRawSensor(boolean enable) {
setRawSensor(enable);
if (enable) {
rawSensorScheduler = startRawSensors();
}
else {
stopRawSensors();
}
}
@Override
protected ZeppOsSupport setTimeFormat(final TransactionBuilder builder) {
final GBPrefs gbPrefs = new GBPrefs(getDevicePrefs());
@ -1308,7 +1136,6 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
final float gx = (x * gravity) / scaleFactor;
final float gy = (y * gravity) / scaleFactor;
final float gz = (z * gravity) / scaleFactor;
sleepAsAndroidSender.onAccelChanged(gx, gy, gz);
LOG.info("Raw sensor g: x={} y={} z={}", gx, gy, gz);
}

View File

@ -143,12 +143,10 @@ public class ZeppOsCalendarService extends AbstractZeppOsService {
buf.putInt(calendarEventSpec.timestamp + calendarEventSpec.durationInSeconds);
// Remind
if (calendarEventSpec.reminders != null && !calendarEventSpec.reminders.isEmpty()) {
buf.putInt((int) (calendarEventSpec.reminders.get(0) / 1000L));
} else {
buf.putInt(0);
}
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
// Repeat
buf.put((byte) 0x00); // ?
buf.put((byte) 0x00); // ?
@ -233,10 +231,7 @@ public class ZeppOsCalendarService extends AbstractZeppOsService {
final int endTime = BLETypeConversions.toUint32(payload, i);
i += 4;
final int reminderTime = BLETypeConversions.toUint32(payload, i);
i += 4;
// ? 00 00 00 00 ff ff ff ff
// ? 00 00 00 00 00 00 00 00 ff ff ff ff
i += 12;
boolean allDay = (payload[i] == 0x01);

View File

@ -49,6 +49,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Menstrual;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationListener;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetPhoneInfoRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendMenstrualModifyTimeRequest;

View File

@ -44,6 +44,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier;
@ -64,8 +65,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProviderType;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
@ -228,6 +228,11 @@ public class HuaweiSupportProvider {
}
public void setGps(boolean start) {
EventHandler handler;
if (isBLE())
handler = leSupport;
else
handler = brSupport;
if (start) {
if (!GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_WORKOUT_SEND_GPS_TO_BAND, false))
return;
@ -236,7 +241,7 @@ public class HuaweiSupportProvider {
gpsParameterRequest.setFinalizeReq(new RequestCallback() {
@Override
public void call() {
GBLocationService.start(getContext(), getDevice(), GBLocationProviderType.GPS, 1000);
GBLocationManager.start(getContext(), handler);
}
});
try {
@ -246,9 +251,9 @@ public class HuaweiSupportProvider {
LOG.error("Failed to get GPS parameters", e);
}
} else
GBLocationService.start(getContext(), getDevice(), GBLocationProviderType.GPS, 1000);
GBLocationManager.start(getContext(), handler);
} else
GBLocationService.stop(getContext(), getDevice());
GBLocationManager.stop(getContext(), handler);
}
public void setGpsParametersResponse(GpsAndTime.GpsParameters.Response response) {

View File

@ -21,9 +21,7 @@ import org.slf4j.LoggerFactory;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket.CryptoException;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
@ -39,11 +37,8 @@ public class SendAccountRequest extends Request {
@Override
protected List<byte[]> createRequest() throws RequestCreationException {
String account = GBApplication
.getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
.getString(HuaweiConstants.PREF_HUAWEI_ACCOUNT, "").trim();
try {
return new AccountRelated.SendAccountToDevice.Request(paramsProvider, account).serialize();
return new AccountRelated.SendAccountToDevice.Request(paramsProvider).serialize();
} catch (CryptoException e) {
throw new RequestCreationException(e);
}

View File

@ -22,8 +22,6 @@ import org.slf4j.LoggerFactory;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket.CryptoException;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
@ -39,14 +37,10 @@ public class SendExtendedAccountRequest extends Request {
@Override
protected List<byte[]> createRequest() throws Request.RequestCreationException {
String account = GBApplication
.getDeviceSpecificSharedPrefs(supportProvider.getDevice().getAddress())
.getString(HuaweiConstants.PREF_HUAWEI_ACCOUNT, "").trim();
try {
return new AccountRelated.SendExtendedAccountToDevice.Request(
paramsProvider,
supportProvider.getHuaweiCoordinator().supportsDiffAccountPairingOptimization(),
account)
supportProvider.getHuaweiCoordinator().supportsDiffAccountPairingOptimization())
.serialize();
} catch (CryptoException e) {
throw new Request.RequestCreationException(e);

View File

@ -106,11 +106,6 @@ public class XiaomiCalendarService extends AbstractXiaomiService {
thisSync.add(calendarEvent);
int notifyMinutesBefore = 0;
if (!calendarEvent.getRemindersAbsoluteTs().isEmpty()) {
notifyMinutesBefore = (int) ((calendarEvent.getBeginSeconds() * 1000L - calendarEvent.getRemindersAbsoluteTs().get(0)) / (1000 * 60));
}
final XiaomiProto.CalendarEvent xiaomiCalendarEvent = XiaomiProto.CalendarEvent.newBuilder()
.setTitle(calendarEvent.getTitle())
.setDescription(StringUtils.ensureNotNull(calendarEvent.getDescription()))
@ -118,7 +113,7 @@ public class XiaomiCalendarService extends AbstractXiaomiService {
.setStart(calendarEvent.getBeginSeconds())
.setEnd((int) (calendarEvent.getEnd() / 1000))
.setAllDay(calendarEvent.isAllDay())
.setNotifyMinutesBefore(notifyMinutesBefore)
.setNotifyMinutesBefore(0) // TODO fetch from event
.build();
calendarSync.addEvent(xiaomiCalendarEvent);

View File

@ -48,8 +48,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiActivitySample;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProviderType;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager;
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
@ -665,7 +664,7 @@ public class XiaomiHealthService extends AbstractXiaomiService {
if (!gpsStarted) {
gpsStarted = true;
gpsFixAcquired = false;
GBLocationService.start(getSupport().getContext(), getSupport().getDevice(), GBLocationProviderType.GPS, 1000);
GBLocationManager.start(getSupport().getContext(), getSupport());
}
gpsTimeoutHandler.removeCallbacksAndMessages(null);
@ -674,7 +673,7 @@ public class XiaomiHealthService extends AbstractXiaomiService {
LOG.debug("Timed out waiting for workout");
gpsStarted = false;
gpsFixAcquired = false;
GBLocationService.stop(getSupport().getContext(), getSupport().getDevice());
GBLocationManager.stop(getSupport().getContext(), getSupport());
}, 5000);
}
@ -697,7 +696,7 @@ public class XiaomiHealthService extends AbstractXiaomiService {
case WORKOUT_FINISHED:
gpsStarted = false;
gpsFixAcquired = false;
GBLocationService.stop(getSupport().getContext(), getSupport().getDevice());
GBLocationManager.stop(getSupport().getContext(), getSupport());
if (startOnPhone) {
OpenTracksController.stopRecording(getSupport().getContext());
}

View File

@ -18,7 +18,6 @@
package nodomain.freeyourgadget.gadgetbridge.service.serial;
import android.location.Location;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.UUID;

View File

@ -500,6 +500,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 = PendingIntentUtils.getActivity(context, 0, notificationIntent, 0, false);
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);

View File

@ -16,25 +16,21 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.util.calendar;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class CalendarEvent {
private final long begin;
private final long end;
private final long id;
private final String title;
private final String description;
private final String location;
private final String calName;
private final String calAccountName;
private final String organizer;
private final int color;
private final boolean allDay;
private List<Long> remindersAbsoluteTs = new ArrayList<>();
private long begin;
private long end;
private long id;
private String title;
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, String calAccountName, int color, boolean allDay, String organizer) {
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;
@ -45,15 +41,6 @@ public class CalendarEvent {
this.calAccountName = calAccountName;
this.color = color;
this.allDay = allDay;
this.organizer = organizer;
}
public List<Long> getRemindersAbsoluteTs() {
return remindersAbsoluteTs;
}
public void setRemindersAbsoluteTs(List<Long> remindersAbsoluteTs) {
this.remindersAbsoluteTs = remindersAbsoluteTs;
}
public long getBegin() {
@ -93,10 +80,6 @@ public class CalendarEvent {
return title;
}
public String getOrganizer() {
return organizer;
}
public String getDescription() {
return description;
}
@ -138,9 +121,7 @@ public class CalendarEvent {
Objects.equals(this.getCalName(), e.getCalName()) &&
Objects.equals(this.getCalAccountName(), e.getCalAccountName()) &&
(this.getColor() == e.getColor()) &&
(this.isAllDay() == e.isAllDay()) &&
Objects.equals(this.getOrganizer(), e.getOrganizer()) &&
Objects.equals(this.getRemindersAbsoluteTs(), e.getRemindersAbsoluteTs());
(this.isAllDay() == e.isAllDay());
} else {
return false;
}
@ -158,8 +139,6 @@ public class CalendarEvent {
result = 31 * result + Objects.hash(calAccountName);
result = 31 * result + Integer.valueOf(color).hashCode();
result = 31 * result + Boolean.valueOf(allDay).hashCode();
result = 31 * result + Objects.hash(organizer);
result = 31 * result + Objects.hash(remindersAbsoluteTs);
return result;
}
}

View File

@ -36,6 +36,7 @@ import java.util.List;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@ -59,12 +60,10 @@ public class CalendarManager {
Instances.TITLE,
Instances.DESCRIPTION,
Instances.EVENT_LOCATION,
Instances.ORGANIZER,
Instances.CALENDAR_DISPLAY_NAME,
CalendarContract.Calendars.ACCOUNT_NAME,
Instances.CALENDAR_COLOR,
Instances.ALL_DAY,
Instances.EVENT_ID //needed for reminders
Instances.ALL_DAY
};
private static final int lookahead_days = 7;
@ -99,54 +98,26 @@ public class CalendarManager {
return calendarEventList;
}
while (evtCursor.moveToNext()) {
long start = evtCursor.getLong(evtCursor.getColumnIndexOrThrow(Instances.BEGIN));
long end = evtCursor.getLong(evtCursor.getColumnIndexOrThrow(Instances.END));
long start = evtCursor.getLong(1);
long end = evtCursor.getLong(2);
if (end == 0) {
LOG.info("no end time, will parse duration string");
Time time = new Time(); //FIXME: deprecated FTW
time.parse(evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.DURATION)));
time.parse(evtCursor.getString(3));
end = start + time.toMillis(false);
}
CalendarEvent calEvent = new CalendarEvent(
start,
end,
evtCursor.getLong(evtCursor.getColumnIndexOrThrow(Instances._ID)),
evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.TITLE)),
evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.DESCRIPTION)),
evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.EVENT_LOCATION)),
evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.CALENDAR_DISPLAY_NAME)),
evtCursor.getString(evtCursor.getColumnIndexOrThrow(CalendarContract.Calendars.ACCOUNT_NAME)),
evtCursor.getInt(evtCursor.getColumnIndexOrThrow(Instances.CALENDAR_COLOR)),
!evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.ALL_DAY)).equals("0"),
evtCursor.getString(evtCursor.getColumnIndexOrThrow(Instances.ORGANIZER))
evtCursor.getLong(0),
evtCursor.getString(4),
evtCursor.getString(5),
evtCursor.getString(6),
evtCursor.getString(7),
evtCursor.getString(8),
evtCursor.getInt(9),
!evtCursor.getString(10).equals("0")
);
// Query reminders for this event
final Cursor reminderCursor = mContext.getContentResolver().query(
CalendarContract.Reminders.CONTENT_URI,
null,
CalendarContract.Reminders.EVENT_ID + " = ?",
new String[]{String.valueOf(evtCursor.getLong(evtCursor.getColumnIndexOrThrow(Instances.EVENT_ID)))},
null
);
if (reminderCursor != null && reminderCursor.getCount() > 0) {
final List<Long> reminders = new ArrayList<>();
while (reminderCursor.moveToNext()) {
int minutes = reminderCursor.getInt(reminderCursor.getColumnIndexOrThrow(CalendarContract.Reminders.MINUTES));
int method = reminderCursor.getInt(reminderCursor.getColumnIndexOrThrow(CalendarContract.Reminders.METHOD));
LOG.debug("Reminder Method: {}, Minutes: {}", method, minutes);
if (method == 1) //METHOD_ALERT
reminders.add(calEvent.getBegin() - minutes * 60 * 1000L);
}
reminderCursor.close();
calEvent.setRemindersAbsoluteTs(reminders);
}
if (!calendarIsBlacklisted(calEvent.getUniqueCalName())) {
calendarEventList.add(calEvent);
} else {

View File

@ -11,38 +11,19 @@ message CalendarService {
message CalendarServiceRequest {
optional uint32 begin = 1;
optional uint32 end = 2;
optional bool include_organizer = 3 [default = false];
optional bool include_title = 4 [default = true];
optional bool include_location = 5 [default = true];
optional bool include_description = 6 [default = false];
optional bool include_start_date = 7 [default = true];
optional bool include_end_date = 8 [default = false];
optional bool include_all_day = 9 [default = false];
optional uint32 max_organizer_length = 10;
optional uint32 max_title_length = 11;
optional uint32 max_location_length = 12;
optional uint32 max_description_length = 13;
optional uint32 max_events = 14;
}
message CalendarServiceResponse {
enum ResponseStatus {
UNKNOWN_RESPONSE_STATUS = 0;
OK = 1;
INVALID_DATE_RANGE = 2;
}
optional ResponseStatus status = 1;
optional uint32 unknown = 1;
repeated CalendarEvent calendar_event = 2;
}
message CalendarEvent {
optional string organizer = 1;
optional string title = 2;
optional string location = 3 [default = ""];
optional string description = 4 [default = ""];
optional uint32 start_date = 5;
optional uint32 end_date = 6;
optional uint32 begin = 5;
optional uint32 end = 6;
optional bool all_day = 7;
repeated uint32 reminder_time_in_secs = 8;
}
}

View File

@ -70,11 +70,12 @@ message CoreService {
optional Status status = 1;
repeated Requested requests = 2;
enum Status {
OK = 1;
UNAVAILABLE = 2;
UNKNOWN3 = 3;
UNKNOWN4 = 4;
OK = 1;
UNAVAILABLE = 2;
UNKNOWN3 = 3;
UNKNOWN4 = 4;
}
message Requested {
@ -82,8 +83,8 @@ message CoreService {
optional RequestedStatus status = 2;
enum RequestedStatus {
OK = 1;
KO = 2;
OK = 1;
KO = 2;
}
}

View File

@ -1483,8 +1483,6 @@
<string name="devicetype_garmin_vivomove_hr">Garmin Vivomove HR</string>
<string name="devicetype_garmin_vivomove_style">VĂ­vomove Style</string>
<string name="devicetype_garmin_instinct_2s">Garmin Instinct 2S</string>
<string name="devicetype_garmin_instinct_2_solar">Garmin Instinct 2 Solar</string>
<string name="devicetype_garmin_instinct_2_soltac">Instinct 2 SolTac</string>
<string name="devicetype_garmin_forerunner_245">Garmin Forerunner 245</string>
<string name="devicetype_garmin_vivoactive_4s">VĂ­voactive 4S</string>
<string name="devicetype_garmin_vivoactive_5">Vivoactive 5</string>
@ -2803,24 +2801,4 @@
<string name="pref_summary_bottom_navigation_bar_off">Switch between main screens only using horizontal swiping</string>
<string name="pref_summary_garmin_default_reply_suffix">Appended in addition to the suffix set in Gadgetbridge</string>
<string name="pref_title_garmin_default_reply_suffix">Use predefined reply suffix</string>
<string name="pref_dashboard_widget_today_upside_down_title">Midnight at bottom</string>
<string name="pref_dashboard_widget_today_upside_down_summary">In 24h mode, draw midnight at the bottom, midday at the top of the chart</string>
<string name="sleepasandroid_settings">Sleep As Android</string>
<string name="pref_sleepasandroid_enable_summary">Enable Sleep As Android integration</string>
<string name="pref_sleepasandroid_device_title">Provider device</string>
<string name="pref_sleepasandroid_device_summary">Select device as Sleep As Android data provider</string>
<string name="pref_sleepasandroid_features_title">Features</string>
<string name="pref_sleepasandroid_features_summary">Support differs from device to device</string>
<string name="pref_sleepasandroid_feat_alarms">Alarms</string>
<string name="pref_sleepasandroid_slot_title">Alarms slot</string>
<string name="pref_sleepasandroid_slot_summary">Which alarm slot to use when setting alarms</string>
<string name="alarm_slot_reset">Alarm slot has been set to default</string>
<string name="pref_sleepasandroid_feat_notifications">Notifications</string>
<string name="pref_sleepasandroid_feat_movement">Accelerometer</string>
<string name="pref_sleepasandroid_feat_heartrate">Heart rate</string>
<string name="pref_sleepasandroid_feat_oximetry">Oximetry</string>
<string name="pref_sleepasandroid_feat_spo2">SPO2</string>
<string name="pref_title_huawei_account">Huawei Account</string>
<string name="pref_summary_huawei_account">Huawei account used in pairing process. Setting it allows to pair without factory reset.</string>
</resources>

View File

@ -46,14 +46,6 @@
android:title="@string/pref_dashboard_widget_today_24h_title"
android:summary="@string/pref_dashboard_widget_today_24h_summary"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="dashboard_widget_today_24h_upside_down"
android:dependency="dashboard_widget_today_24h"
android:layout="@layout/preference_checkbox"
android:title="@string/pref_dashboard_widget_today_upside_down_title"
android:summary="@string/pref_dashboard_widget_today_upside_down_summary"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="dashboard_widget_today_2columns"

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
android:icon="@drawable/ic_vpn_key"
android:key="huawei_account"
android:maxLength="17"
android:summary="@string/pref_summary_huawei_account"
android:title="@string/pref_title_huawei_account" />
</androidx.preference.PreferenceScreen>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen
android:icon="@drawable/ic_gps_location"
android:key="pref_screen_location"
android:persistent="false"
android:title="@string/pref_header_location">
</PreferenceScreen>
</androidx.preference.PreferenceScreen>

View File

@ -63,11 +63,6 @@
android:title="@string/bottom_nav_dashboard"
app:iconSpaceReserved="false" />
<Preference
android:key="pref_category_sleepasandroid"
android:title="@string/sleepasandroid_settings"
app:iconSpaceReserved="false" />
<PreferenceScreen
android:key="pref_screen_theme"
android:title="@string/pref_title_theme"

View File

@ -1,78 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
android:key="pref_key_sleepasandroid_general"
android:title="@string/pref_header_general"
app:iconSpaceReserved="false">
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_key_sleepasandroid_enable"
android:layout="@layout/preference_checkbox"
android:summary="@string/pref_sleepasandroid_enable_summary"
android:title="@string/function_enabled"
app:iconSpaceReserved="false" />
<ListPreference
android:key="sleepasandroid_device"
android:entries="@array/empty_array"
android:entryValues="@array/empty_array"
android:title="@string/pref_sleepasandroid_device_title"
android:summary="@string/pref_sleepasandroid_device_summary"
app:iconSpaceReserved="false" />
<ListPreference
android:key="sleepasandroid_alarm_slot"
android:entries="@array/empty_array"
android:entryValues="@array/empty_array"
android:title="@string/pref_sleepasandroid_slot_title"
android:summary="@string/pref_sleepasandroid_slot_summary"
android:enabled="false"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_key_sleepasandroid_features"
android:title="@string/pref_sleepasandroid_features_title"
android:summary="@string/pref_sleepasandroid_features_summary"
app:iconSpaceReserved="false">
<SwitchPreferenceCompat
android:key="pref_key_sleepasandroid_feat_alarms"
android:title="@string/pref_sleepasandroid_feat_alarms"
app:iconSpaceReserved="false"
android:defaultValue="true">
</SwitchPreferenceCompat>
<SwitchPreferenceCompat
android:key="pref_key_sleepasandroid_feat_notifications"
android:title="@string/pref_sleepasandroid_feat_notifications"
app:iconSpaceReserved="false"
android:defaultValue="true">
</SwitchPreferenceCompat>
<SwitchPreferenceCompat
android:key="pref_key_sleepasandroid_feat_movement"
android:title="@string/pref_sleepasandroid_feat_movement"
app:iconSpaceReserved="false"
android:defaultValue="true"
android:enabled="false">
</SwitchPreferenceCompat>
<SwitchPreferenceCompat
android:key="pref_key_sleepasandroid_feat_hr"
android:title="@string/pref_sleepasandroid_feat_heartrate"
app:iconSpaceReserved="false"
android:defaultValue="true"
android:enabled="false">
</SwitchPreferenceCompat>
<SwitchPreferenceCompat
android:key="pref_key_sleepasandroid_feat_oximetry"
android:title="@string/pref_sleepasandroid_feat_oximetry"
app:iconSpaceReserved="false"
android:defaultValue="true"
android:enabled="false">
</SwitchPreferenceCompat>
<SwitchPreferenceCompat
android:key="pref_key_sleepasandroid_feat_spo2"
android:title="@string/pref_sleepasandroid_feat_spo2"
app:iconSpaceReserved="false"
android:defaultValue="true"
android:enabled="false">
</SwitchPreferenceCompat>
</PreferenceCategory>
</PreferenceScreen>

View File

@ -25,25 +25,22 @@ public class CalendarEventTest extends TestBase {
@Test
public void testHashCode() {
CalendarEvent c1 =
new CalendarEvent(BEGIN, END, ID_1, "something", null, null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false, null);
new CalendarEvent(BEGIN, END, ID_1, "something", null, null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false);
CalendarEvent c2 =
new CalendarEvent(BEGIN, END, ID_1, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false, null);
new CalendarEvent(BEGIN, END, ID_1, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false);
CalendarEvent c3 =
new CalendarEvent(BEGIN, END, ID_1, null, null, "something", CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false, null);
CalendarEvent c4 =
new CalendarEvent(BEGIN, END, ID_1, null, null, "something", CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false, "some");
new CalendarEvent(BEGIN, END, ID_1, null, null, "something", CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false);
assertEquals(c1.hashCode(), c1.hashCode());
assertNotEquals(c1.hashCode(), c2.hashCode());
assertNotEquals(c2.hashCode(), c3.hashCode());
assertNotEquals(c3.hashCode(), c4.hashCode());
}
@Test
public void testSync() {
List<CalendarEvent> eventList = new ArrayList<>();
eventList.add(new CalendarEvent(BEGIN, END, ID_1, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false, null));
eventList.add(new 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);
@ -52,7 +49,7 @@ public class CalendarEventTest extends TestBase {
testCR.syncCalendar(eventList);
eventList.add(new CalendarEvent(BEGIN, END, ID_2, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false, null));
eventList.add(new CalendarEvent(BEGIN, END, ID_2, null, "something", null, CALNAME_1, CALACCOUNTNAME_1, COLOR_1, false));
testCR.syncCalendar(eventList);
CalendarSyncStateDao calendarSyncStateDao = daoSession.getCalendarSyncStateDao();