mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-26 18:45:49 +01:00
Merge branch 'master' of codeberg.org:Freeyourgadget/Gadgetbridge
This commit is contained in:
commit
d98f8d7484
8
.github/ISSUE_TEMPLATE.md
vendored
8
.github/ISSUE_TEMPLATE.md
vendored
@ -5,9 +5,9 @@ about: Create a report to help us improve
|
||||
---
|
||||
|
||||
#### Before reporting a bug, please confirm the following:
|
||||
- [ ] I have read the [wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] I have searched the [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] If you upload an image or other content, please make sure you have read and understood the [github policies and terms of services](https://help.github.com/articles/github-terms-of-service/#1-responsibility-for-user-generated-content)
|
||||
- [ ] I have read the [wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] I have searched the [issues](https://codeberg.org/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] If you upload an image or other content, please make sure you have read and understood the [Codeberg Terms of Use](https://codeberg.org/codeberg/org/src/branch/master/TermsOfUse.md)
|
||||
|
||||
### I got Gadgetbridge from:
|
||||
* [ ] F-Droid
|
||||
@ -16,7 +16,7 @@ about: Create a report to help us improve
|
||||
If you got it from Google Play, please note [that version](https://github.com/TaaviE/Gadgetbridge) is unofficial and not supported here; it's also often quite outdated. Please switch to one of the above versions if you can.
|
||||
|
||||
#### Your issue is:
|
||||
*If possible, please attach [logs](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Log-Files)! that might help identifying the problem.*
|
||||
*If possible, please attach [logs](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Log-Files)! that might help identifying the problem.*
|
||||
|
||||
#### Your wearable device is:
|
||||
|
||||
|
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -5,9 +5,9 @@ about: Create a report to help us improve
|
||||
---
|
||||
|
||||
#### Before reporting a bug, please confirm the following:
|
||||
- [ ] I have read the [wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] I have searched the [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] If you upload an image or other content, please make sure you have read and understood the [github policies and terms of services](https://help.github.com/articles/github-terms-of-service/#1-responsibility-for-user-generated-content)
|
||||
- [ ] I have read the [wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] I have searched the [issues](https://codeberg.org/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] If you upload an image or other content, please make sure you have read and understood the [Codeberg Terms of Use](https://codeberg.org/codeberg/org/src/branch/master/TermsOfUse.md)
|
||||
|
||||
### I got Gadgetbridge from:
|
||||
* [ ] F-Droid
|
||||
@ -16,7 +16,7 @@ about: Create a report to help us improve
|
||||
If you got it from Google Play, please note [that version](https://github.com/TaaviE/Gadgetbridge) is unofficial and not supported here; it's also often quite outdated. Please switch to one of the above versions if you can.
|
||||
|
||||
#### Your issue is:
|
||||
*If possible, please attach [logs](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Log-Files)! that might help identifying the problem.*
|
||||
*If possible, please attach [logs](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Log-Files)! that might help identifying the problem.*
|
||||
|
||||
#### Your wearable device is:
|
||||
|
||||
|
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -5,12 +5,12 @@ about: Suggest an idea for this project
|
||||
---
|
||||
|
||||
#### Before requesting a new feature, please confirm the following:
|
||||
- [ ] I have read the [wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] I have searched the [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] If you upload an image or other content, please make sure you have read and understood the [github policies and terms of services](https://help.github.com/articles/github-terms-of-service/#1-responsibility-for-user-generated-content)
|
||||
- [ ] I have read the [wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] I have searched the [issues](https://codeberg.org/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] If you upload an image or other content, please make sure you have read and understood the [Codeberg Terms of Use](https://codeberg.org/codeberg/org/src/branch/master/TermsOfUse.md)
|
||||
|
||||
#### Your issue is:
|
||||
*If applicable, please attach [logs](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Log-Files)*
|
||||
*If applicable, please attach [logs](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Log-Files)*
|
||||
|
||||
#### Your wearable device is:
|
||||
|
||||
|
@ -19,10 +19,10 @@ android:
|
||||
- tools
|
||||
|
||||
# The BuildTools version used by your project
|
||||
- build-tools-28.0.3
|
||||
- build-tools-29.0.3
|
||||
|
||||
# The SDK version used to compile your project
|
||||
- android-28
|
||||
- android-29
|
||||
|
||||
# Additional components
|
||||
- extra-android-m2repository
|
||||
|
86
CHANGELOG.md
86
CHANGELOG.md
@ -1,4 +1,90 @@
|
||||
### Changelog
|
||||
|
||||
#### 0.46.0
|
||||
* Initial support for Mi Band 5
|
||||
* Initial support for TLW64
|
||||
* Amazfit GTR/GTS: Fix broken activity data on newer firmwares
|
||||
* Big refactoring of the device discovery activity (See PR #1927 description for details)
|
||||
* Add about screen
|
||||
* New icon for Amazfit Bip
|
||||
* Avoid duplicated entries in preferred media player selection
|
||||
* Avoid a lot of crashes and improve error handling in various places
|
||||
|
||||
#### 0.45.1
|
||||
* Amazfit GTR/GTS: Fix connection issue with latest firmwares (probably other Huami devices also affected)
|
||||
* Add experimental support for TinyWeatherForecastGermany
|
||||
|
||||
#### 0.45.0
|
||||
* Initial support for Amazfit T-Rex
|
||||
* Amazfit Bip S: Support installation of latest .res
|
||||
* Amazfit Bip S: Support longer notification messages
|
||||
* Huami: Limit weather forecast to 7 days to fix problems with weather notificaon 0.3.11
|
||||
* Huami: Improve music playback information
|
||||
* Huami: Ensure cutting strings on UTF-8 border
|
||||
* Stop incoming call notification when VoIP calls are missed
|
||||
* Fix a crash when with Farsi translation
|
||||
|
||||
#### 0.44.2
|
||||
* Huami: Support flashing newer GPS firmware and GPS ALM
|
||||
* Amazfit Bip S: Support music control
|
||||
* Amazfit Bip S: Support flashing firmware, res, gps firmware, watchfaces, fonts and GPS CEP
|
||||
* Amazfit Bip S: Allow setting high MTU (much faster firmware installation, default off since it does not work for some)
|
||||
* Amazfit Bip S: remove disconnect notification and button action settings (they do not work)
|
||||
* Mi Band 4 (possibly others): Fix detected RES version being always 69 for non-whitelisted res files
|
||||
* Fossil Hybrid HR: Add last notification widget
|
||||
* Try to fix vanishing incoming call information when VoIP call support is enabled
|
||||
* Allow setting device aliases (useful if you manage multiple ones of the same type)
|
||||
|
||||
#### Version 0.44.1
|
||||
* Amazfit Bip S: Support setting shortcuts
|
||||
* Amazfit Bip S: Fix setting display items
|
||||
* Amazfit Bip S: Fix incoming call notification
|
||||
* Huami: Fix menu items vanishing from the device when they were never configured through Gadgetbridge
|
||||
* Lenovo Watch9: Fix launch of wrong calibration activity
|
||||
* Reduce calls to onSetMusicInfo/onSetMusicState when playing music
|
||||
|
||||
#### Version 0.44.0
|
||||
* Initial support for WatchX(Plus)
|
||||
* Add support for Amazfit GTR Lite (untested and incomplete)
|
||||
* Fossil Hybrid HR: Fix some issues with custom widgets
|
||||
* Fossil Hybrid HR: Allow setting alarm titles and descriptions
|
||||
* Fossil Hybrid HR: Fix step data parsing
|
||||
* Amazfit GTS: Fix setting menu items with low MTU
|
||||
* Amazfit GTR: Allow setting menu item like GTS
|
||||
* ZeTime: Support setting the watch language
|
||||
* ZeTime: Support rejecting calls
|
||||
* ZeTime: Try to fix weather conditions on newer firmware
|
||||
* ZeTime: Fix could not synchronize calendar on connect
|
||||
* ZeTime: Fix calendar event time and date
|
||||
* ZeTime: Send up to 16 upcoming calendar events on connect if option is enabled
|
||||
* Allow set light/dark theme according to system settings (new default)
|
||||
|
||||
#### Version 0.43.3
|
||||
* Fossil Hybrid HR: Initial support for activity tracking (no sleep yet)
|
||||
* Fossil Hybrid HR: Support setting alarms on newer firmware
|
||||
* Amazfit GTR/GTS: Fix flashing watchfaces and maybe firmware/res update (still untested)
|
||||
* Amazfit GTS: Support enabling/disabling menu items on the watch
|
||||
* Implement transliteration for Korean
|
||||
|
||||
#### Version 0.43.2
|
||||
* Fossil Hybrid HR: Allow choosing and cropping image to be set as watch background
|
||||
* Fossil Hybrid HR: Option to draw circles around widgets
|
||||
* Fossil Hybrid HR: Experimenal firmware update support
|
||||
* Fossil Hybrid HR: Fix vibration strength setting
|
||||
* Huami: Do not display firmware information and whitelist information when flashing watchfaces
|
||||
* Huami: Disable air quality indicator on Huami devices instead of showing 0
|
||||
* Bangle.js: Change encoded char set to match Espruino's 8 bit fonts
|
||||
* Steps/Sleep averages: Skip days with zero data
|
||||
|
||||
#### Version 0.43.1
|
||||
* Initial support for Amazfit Bip S (incomplete, needs the official app once to obtain the pairing key)
|
||||
* Amazift Bip Lite: Allow relaxing firmware checks to allow flashing of the regular Bip firmware (for the brave)
|
||||
* Fossil Hybrid HR: Fix notification history on newer firmwares
|
||||
* Fossil Hybrid HR: Add option to disable widget circle
|
||||
* Bangle.js: Don't set time if the option is turned off in settings
|
||||
* Bangle.js: DST and time zone fixes
|
||||
* Add Arabic-style Eastern Arabic numerals to transliteration
|
||||
|
||||
#### Version 0.43.0
|
||||
* Initial support for Fossil Hybrid HR (needs complicated key extraction, read wiki)
|
||||
* Fossil: Allow switching off the Q Icon and use the default Gadgetbridge icon
|
||||
|
46
FEATURES.md
46
FEATURES.md
@ -1,28 +1,28 @@
|
||||
## Feature Matrix
|
||||
|
||||
| | Pebble OG | Pebble Time/2 | Mi Band | Mi Band 2 | Mi Band 3 | Amazfit Bip | Amazfit Cor |
|
||||
|-----------------------------------| ----------|---------------|---------|-----------|-----------|-------------|-------------|
|
||||
|Calls Notification | YES | YES | YES | YES | YES | YES | YES |
|
||||
|Reject Calls | YES | YES | NO | NO | YES | YES | YES |
|
||||
|Accept Calls | NO(2) | NO(2) | NO | NO | NO | NO | NO |
|
||||
|Generic Notification | YES | YES | YES | YES | YES | YES | YES |
|
||||
|Dismiss Notifications on Phone | YES | YES | NO | NO | NO | NO | NO |
|
||||
|Predefined Replies | YES | YES | NO | NO | NO | NO | NO |
|
||||
|Voice Replies | N/A | NO(3) | N/A | N/A | N/A | N/A | N/A |
|
||||
|Calendar Sync | YES | YES | NO | NO | NO | NO(3) | NO |
|
||||
|Configure alarms from Gadgetbridge | NO | NO | YES | YES | YES | YES | YES |
|
||||
|Smart alarms | NO(1) | YES | YES | NO | NO | NO | NO |
|
||||
|Weather | NO(1) | YES | NO | NO | YES | YES | YES |
|
||||
|Activity Tracking | NO(1) | YES | YES | YES | YES | YES | YES |
|
||||
|GPS tracks import | NO | NO | NO | NO | NO | YES | NO |
|
||||
|Sleep Tracking | NO(1) | YES | YES | YES | YES | YES | YES |
|
||||
|HR Tracking | N/A | YES | YES | YES | YES | YES | YES |
|
||||
|Realtime Activity Tracking | NO | NO | YES | YES | YES | YES | YES |
|
||||
|Music Control | YES | YES | NO | NO | NO | NO | YES |
|
||||
|Watchapp/face Installation | YES | YES | NO | NO | NO | YES | YES |
|
||||
|Firmware Installation | YES | YES | YES | YES | YES | YES | YES |
|
||||
|Taking Screenshots | YES | YES | NO | NO | NO | NO | NO |
|
||||
|Support Android Companion Apps | YES | YES | NO | NO | NO | NO | NO |
|
||||
| | Pebble OG | Pebble Time/2 | Mi Band | Mi Band 2 | Mi Band 3 | Mi Band 4/5 | Amazfit Bip | Amazfit Cor |
|
||||
|-----------------------------------| ----------|---------------|---------|-----------|-----------|-------------|-------------|-------------|
|
||||
|Calls Notification | YES | YES | YES | YES | YES | YES | YES | YES |
|
||||
|Reject Calls | YES | YES | NO | NO | YES | YES | YES | YES |
|
||||
|Accept Calls | NO(2) | NO(2) | NO | NO | NO | NO | NO | NO |
|
||||
|Generic Notification | YES | YES | YES | YES | YES | YES | YES | YES |
|
||||
|Dismiss Notifications on Phone | YES | YES | NO | NO | NO | NO | NO | NO |
|
||||
|Predefined Replies | YES | YES | NO | NO | NO | NO | NO | NO |
|
||||
|Voice Replies | N/A | NO(3) | N/A | N/A | N/A | N/A | N/A | N/A |
|
||||
|Calendar Sync | YES | YES | NO | NO | NO | NO | NO(3) | NO |
|
||||
|Configure alarms from Gadgetbridge | NO | NO | YES | YES | YES | YES(1) | YES | YES |
|
||||
|Smart alarms | NO(1) | YES | YES | NO | NO | NO | NO | NO |
|
||||
|Weather | NO(1) | YES | NO | NO | YES | YES | YES | YES |
|
||||
|Activity Tracking | NO(1) | YES | YES | YES | YES | YES | YES | YES |
|
||||
|GPS tracks import | NO | NO | NO | NO | NO | NO | YES | NO |
|
||||
|Sleep Tracking | NO(1) | YES | YES | YES | YES | YES | YES | YES |
|
||||
|HR Tracking | N/A | YES | YES | YES | YES | YES | YES | YES |
|
||||
|Realtime Activity Tracking | NO | NO | YES | YES | YES | YES | YES | YES |
|
||||
|Music Control | YES | YES | NO | NO | NO | YES | NO | YES |
|
||||
|Watchapp/face Installation | YES | YES | NO | NO | NO | YES | YES | YES |
|
||||
|Firmware Installation | YES | YES | YES | YES | YES | YES | YES | YES |
|
||||
|Taking Screenshots | YES | YES | NO | NO | NO | NO | NO | NO |
|
||||
|Support Android Companion Apps | YES | YES | NO | NO | NO | NO | NO | NO |
|
||||
|
||||
(1) Possible via 3rd Party Watchapp
|
||||
(2) Theoretically possible (works on iOS, would need lot of work)
|
||||
|
@ -6,9 +6,9 @@ archivesBaseName = 'gadgetbridge-daogenerator'
|
||||
//version = '0.9.2-SNAPSHOT'
|
||||
|
||||
dependencies {
|
||||
// compile 'org.greenrobot:greendao-generator:2.2.0'
|
||||
// compile project(":DaoGenerator")
|
||||
compile 'com.github.Freeyourgadget:greendao:1998d7cd2d21f662c6044f6ccf3b3a251bbad341'
|
||||
// implementation 'org.greenrobot:greendao-generator:2.2.0'
|
||||
// implementation project(":DaoGenerator")
|
||||
implementation 'com.github.Freeyourgadget:greendao:1998d7cd2d21f662c6044f6ccf3b3a251bbad341'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
@ -43,7 +43,7 @@ public class GBDaoGenerator {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Schema schema = new Schema(24, MAIN_PACKAGE + ".entities");
|
||||
Schema schema = new Schema(30, MAIN_PACKAGE + ".entities");
|
||||
|
||||
Entity userAttributes = addUserAttributes(schema);
|
||||
Entity user = addUserInfo(schema, userAttributes);
|
||||
@ -71,6 +71,11 @@ public class GBDaoGenerator {
|
||||
addZeTimeActivitySample(schema, user, device);
|
||||
addID115ActivitySample(schema, user, device);
|
||||
addJYouActivitySample(schema, user, device);
|
||||
addWatchXPlusHealthActivitySample(schema, user, device);
|
||||
addWatchXPlusHealthActivityKindOverlay(schema, user, device);
|
||||
addTLW64ActivitySample(schema, user, device);
|
||||
|
||||
addHybridHRActivitySample(schema, user, device);
|
||||
addCalendarSyncState(schema, device);
|
||||
addAlarms(schema, user, device);
|
||||
|
||||
@ -78,7 +83,7 @@ public class GBDaoGenerator {
|
||||
|
||||
addNotificationFilterEntry(schema, notificationFilter);
|
||||
|
||||
addBipActivitySummary(schema, user, device);
|
||||
addActivitySummary(schema, user, device);
|
||||
|
||||
new DaoGenerator().generateAll(schema, "app/src/main/java");
|
||||
}
|
||||
@ -167,6 +172,7 @@ public class GBDaoGenerator {
|
||||
device.addStringProperty("identifier").notNull().unique().javaDocGetterAndSetter("The fixed identifier, i.e. MAC address of the device.");
|
||||
device.addIntProperty("type").notNull().javaDocGetterAndSetter("The DeviceType key, i.e. the GBDevice's type.");
|
||||
device.addStringProperty("model").javaDocGetterAndSetter("An optional model, further specifying the kind of device-");
|
||||
device.addStringProperty("alias");
|
||||
Property deviceId = deviceAttributes.addLongProperty("deviceId").notNull().getProperty();
|
||||
// sorted by the from-date, newest first
|
||||
Property deviceAttributesSortProperty = getPropertyByName(deviceAttributes, VALID_FROM_UTC);
|
||||
@ -341,6 +347,63 @@ public class GBDaoGenerator {
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addHybridHRActivitySample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "HybridHRActivitySample");
|
||||
activitySample.implementsSerializable();
|
||||
|
||||
addCommonActivitySampleProperties("AbstractHybridHRActivitySample", activitySample, user, device);
|
||||
|
||||
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty("calories").notNull();
|
||||
activitySample.addIntProperty("variability").notNull();
|
||||
activitySample.addIntProperty("max_variability").notNull();
|
||||
activitySample.addIntProperty("heartrate_quality").notNull();
|
||||
activitySample.addBooleanProperty("active").notNull();
|
||||
activitySample.addByteProperty("wear_type").notNull();
|
||||
addHeartRateProperties(activitySample);
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addWatchXPlusHealthActivitySample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "WatchXPlusActivitySample");
|
||||
activitySample.implementsSerializable();
|
||||
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
|
||||
activitySample.addByteArrayProperty("rawWatchXPlusHealthData");
|
||||
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
|
||||
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
addHeartRateProperties(activitySample);
|
||||
activitySample.addIntProperty("distance");
|
||||
activitySample.addIntProperty("calories");
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addWatchXPlusHealthActivityKindOverlay(Schema schema, Entity user, Entity device) {
|
||||
Entity activityOverlay = addEntity(schema, "WatchXPlusHealthActivityOverlay");
|
||||
|
||||
activityOverlay.addIntProperty(TIMESTAMP_FROM).notNull().primaryKey();
|
||||
activityOverlay.addIntProperty(TIMESTAMP_TO).notNull().primaryKey();
|
||||
activityOverlay.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
|
||||
Property deviceId = activityOverlay.addLongProperty("deviceId").primaryKey().notNull().getProperty();
|
||||
activityOverlay.addToOne(device, deviceId);
|
||||
|
||||
Property userId = activityOverlay.addLongProperty("userId").notNull().getProperty();
|
||||
activityOverlay.addToOne(user, userId);
|
||||
activityOverlay.addByteArrayProperty("rawWatchXPlusHealthData");
|
||||
|
||||
return activityOverlay;
|
||||
}
|
||||
|
||||
private static Entity addTLW64ActivitySample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "TLW64ActivitySample");
|
||||
activitySample.implementsSerializable();
|
||||
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
|
||||
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
|
||||
activitySample.setSuperclass(superClass);
|
||||
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
|
||||
@ -391,6 +454,8 @@ public class GBDaoGenerator {
|
||||
alarm.addIntProperty("hour").notNull();
|
||||
alarm.addIntProperty("minute").notNull();
|
||||
alarm.addBooleanProperty("unused").notNull();
|
||||
alarm.addStringProperty("title");
|
||||
alarm.addStringProperty("description");
|
||||
alarm.addToOne(user, userId);
|
||||
alarm.addToOne(device, deviceId);
|
||||
}
|
||||
@ -419,7 +484,7 @@ public class GBDaoGenerator {
|
||||
return notificatonFilter;
|
||||
}
|
||||
|
||||
private static void addBipActivitySummary(Schema schema, Entity user, Entity device) {
|
||||
private static void addActivitySummary(Schema schema, Entity user, Entity device) {
|
||||
Entity summary = addEntity(schema, "BaseActivitySummary");
|
||||
summary.implementsInterface(ACTIVITY_SUMMARY);
|
||||
summary.addIdProperty();
|
||||
@ -442,6 +507,8 @@ public class GBDaoGenerator {
|
||||
summary.addToOne(device, deviceId);
|
||||
Property userId = summary.addLongProperty("userId").notNull().codeBeforeGetter(OVERRIDE).getProperty();
|
||||
summary.addToOne(user, userId);
|
||||
summary.addStringProperty("summaryData");
|
||||
summary.addByteArrayProperty("rawSummaryData");
|
||||
}
|
||||
|
||||
private static Property findProperty(Entity entity, String propertyName) {
|
||||
|
@ -25,3 +25,6 @@ Creative Commons Attribution 3.0 Unported license (CC BY-3.0):
|
||||
|
||||
Creative Commons Attribution 3.0 United States (CC BY-3.0 US):
|
||||
ic_donate by Peter van Driel https://thenounproject.com/term/donate/239009/
|
||||
|
||||
Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0):
|
||||
ic_device_amazfit_bip by Michael quelbs@gmail.com
|
||||
|
11
README.md
11
README.md
@ -27,13 +27,15 @@ vendor's servers.
|
||||
|
||||
[List of changes](https://codeberg.org/Freeyourgadget/Gadgetbridge/src/master/CHANGELOG.md)
|
||||
|
||||
## Supported Devices (Some of them WIP and some of them without maintainer)
|
||||
## Supported Devices (WARNING: Some of them WIP and some of them without maintainer)
|
||||
* Amazfit Bip [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip)
|
||||
* Amazfit Bip Lite (WARNING: NEEDS MI FIT WITH ACCOUNT ONCE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip-Lite)
|
||||
* Amazfit Bip S (WARNING: NEEDS MI FIT WITH ACCOUNT ONCE)
|
||||
* Amazfit Cor [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Cor)
|
||||
* Amazfit Cor 2 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Cor-2)
|
||||
* Amazfit GTR (WARNING: NEEDS MI FIT WITH ACCOUNT ONCE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTR)
|
||||
* Amazfit GTS (WARNING: NEEDS MI FIT WITH ACCOUNT ONCE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTS)
|
||||
* Amazfit T-Rex (WARNING: NEEDS MI FIT WITH ACCOUNT ONCE)
|
||||
* BFH-16
|
||||
* Casio GB-6900B
|
||||
* Fossil Hybrid HR (WARNING: NEEDS FOSSIL APP WITH ACCOUNT ONCE AND COMPLICATED PROCEDURE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Fossil-Hybrid-HR)
|
||||
@ -43,17 +45,20 @@ vendor's servers.
|
||||
* ID115
|
||||
* JYou Y5
|
||||
* Lenovo Watch 9
|
||||
* Lenovo Watch X (Plus) [Wiki](https://codeberg.org/mamutcho/Gadgetbridge/wiki)
|
||||
* Liveview
|
||||
* Makibes HR3
|
||||
* Mi Band, Mi Band 1A, Mi Band 1S [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band)
|
||||
* Mi Band 2 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-2)
|
||||
* Mi Band 3 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-3)
|
||||
* Mi Band 4 (NOT RECOMMENDED, NEEDS MI FIT WITH ACCOUNT AND ROOT ONCE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-4)
|
||||
* Mi Band 4 (WARNING: NEEDS MI FIT WITH ACCOUNT AND ROOT ONCE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-4)
|
||||
* Mi Band 5 (WARNING: NEEDS MI FIT WITH ACCOUNT AND ROOT ONCE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-5)
|
||||
* Mi Scale 2 (Currently only displays a toast after stepping on the scale)
|
||||
* NO.1 F1
|
||||
* Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, Pebble Time Round [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Pebble)
|
||||
* Pebble 2 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Pebble)
|
||||
* Teclast H10, H30
|
||||
* TLW64
|
||||
* XWatch (Affordable Chinese Casio-like smartwatches)
|
||||
* Vibratissimo (Experimental)
|
||||
* ZeTime [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/MyKronoz-ZeTime)
|
||||
@ -78,6 +83,7 @@ Please see [FEATURES.md](https://codeberg.org/Freeyourgadget/Gadgetbridge/src/ma
|
||||
* Sebastian Kranz (ZeTime)
|
||||
* Vadim Kaushan (ID115)
|
||||
* "maxirnilian" (Lenovo Watch 9)
|
||||
* "ksiwczynski", "mkusnierz", "mamutcho" (Lenovo Watch X Plus)
|
||||
* Andreas Böhler (Casio GB-6900B)
|
||||
* Jean-François Greffier (Mi Scale 2)
|
||||
* Johannes Schmitt (BFH-16)
|
||||
@ -86,6 +92,7 @@ Please see [FEATURES.md](https://codeberg.org/Freeyourgadget/Gadgetbridge/src/ma
|
||||
* Gordon Williams (Bangle.js)
|
||||
* Pavel Elagin (JYou Y5)
|
||||
* Taavi Eomäe (iTag)
|
||||
* Erik Bloß (TLW64)
|
||||
|
||||
## Contribute
|
||||
|
||||
|
@ -16,17 +16,17 @@ android {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '29.0.3'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "nodomain.freeyourgadget.gadgetbridge"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
targetSdkVersion 29
|
||||
|
||||
// Note: always bump BOTH versionCode and versionName!
|
||||
versionName "0.43.0"
|
||||
versionCode 169
|
||||
versionName "0.46.0"
|
||||
versionCode 178
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
buildTypes {
|
||||
@ -68,7 +68,7 @@ dependencies {
|
||||
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||
implementation "androidx.preference:preference:1.1.0"
|
||||
implementation "androidx.preference:preference:1.1.1"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||
|
11
app/proguard-rules.pro
vendored
11
app/proguard-rules.pro
vendored
@ -31,4 +31,13 @@
|
||||
|
||||
-keep class **$Properties
|
||||
|
||||
-keep class **$Properties { *; }
|
||||
-keep class **$Properties { *; }
|
||||
|
||||
-keep class **.gadgetbridge.database.schema.* { *; }
|
||||
|
||||
# Keep dependency android-emojify (io.wax911.emojify) uses
|
||||
-keep class org.hamcrest.** { *; }
|
||||
|
||||
# Keep logback classes
|
||||
-keep class ch.qos.** { *; }
|
||||
-keep class org.slf4j.** { *; }
|
@ -1,33 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="nodomain.freeyourgadget.gadgetbridge">
|
||||
|
||||
<!--
|
||||
Comment in for testing Pebble Emulator
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
-->
|
||||
<!-- Used for Bluetooth access -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<!-- Since Android 10 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_CALL_LOG" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
||||
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
|
||||
<uses-permission
|
||||
android:name="android.permission.MEDIA_CONTENT_CONTROL"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
<!-- Used for background service -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" /> <!-- Used for reverse find device -->
|
||||
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
|
||||
|
||||
<!-- Used for reverse find device -->
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<!-- Used for weather provider access -->
|
||||
<uses-permission android:name="cyanogenmod.permission.ACCESS_WEATHER_MANAGER" />
|
||||
<uses-permission android:name="cyanogenmod.permission.READ_WEATHER" />
|
||||
<uses-permission android:name="lineageos.permission.ACCESS_WEATHER_MANAGER" />
|
||||
<uses-permission android:name="lineageos.permission.READ_WEATHER" />
|
||||
<uses-permission android:name="org.omnirom.omnijaws.READ_WEATHER" />
|
||||
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:required="true" />
|
||||
@ -37,6 +57,9 @@
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.software.companion_device_setup"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:name=".GBApplication"
|
||||
@ -46,7 +69,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/GadgetbridgeTheme">
|
||||
<activity android:name=".devices.qhybrid.WidgetSettingsActivity"></activity>
|
||||
<activity android:name=".devices.qhybrid.WidgetSettingsActivity" />
|
||||
<activity
|
||||
android:name=".activities.ControlCenterv2"
|
||||
android:label="@string/title_activity_controlcenter"
|
||||
@ -292,6 +315,7 @@
|
||||
|
||||
<data android:mimeType="application/octet-stream" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- to receive firmwares from the download content provider if recognized as zip -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
@ -301,6 +325,7 @@
|
||||
<data android:mimeType="application/zip" />
|
||||
<data android:mimeType="application/x-zip-compressed" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- to receive files from the "share" intent -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
@ -345,13 +370,21 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".externalevents.BluetoothStateChangeReceiver"
|
||||
android:exported="false">
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BLUETOOTH,android.permission.BLUETOOTH_ADMIN">
|
||||
<intent-filter>
|
||||
<action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
|
||||
<action android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
|
||||
|
||||
<action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
|
||||
<action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
|
||||
<action android:name="android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".service.receivers.GBMusicControlReceiver"
|
||||
android:exported="false">
|
||||
@ -369,7 +402,7 @@
|
||||
<receiver
|
||||
android:name=".database.PeriodicExporter"
|
||||
android:enabled="true"
|
||||
android:exported="false"></receiver>
|
||||
android:exported="false" />
|
||||
<!--
|
||||
forcing the DebugActivity to portrait mode avoids crashes with the progress
|
||||
dialog when changing orientation
|
||||
@ -380,6 +413,19 @@
|
||||
android:parentActivityName=".activities.ControlCenterv2"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name=".activities.AboutActivity"
|
||||
android:label="@string/about_activity_title"
|
||||
android:parentActivityName=".activities.ControlCenterv2"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name=".activities.ActivitySummaryDetail"
|
||||
android:label="@string/activity_summary_detail"
|
||||
android:parentActivityName=".activities.ActivitySummariesActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
|
||||
<activity
|
||||
android:name=".activities.DbManagementActivity"
|
||||
android:label="@string/title_activity_db_management"
|
||||
@ -405,6 +451,12 @@
|
||||
<activity
|
||||
android:name=".devices.watch9.Watch9CalibrationActivity"
|
||||
android:label="@string/title_activity_watch9_calibration" />
|
||||
<activity
|
||||
android:name=".devices.lenovo.LenovoWatchPairingActivity"
|
||||
android:label="@string/title_activity_watch9_pairing" />
|
||||
<activity
|
||||
android:name=".devices.lenovo.LenovoWatchCalibrationActivity"
|
||||
android:label="@string/title_activity_LenovoWatch_calibration" />
|
||||
<activity
|
||||
android:name=".activities.charts.ChartsActivity"
|
||||
android:label="@string/title_activity_charts"
|
||||
@ -438,6 +490,7 @@
|
||||
android:name=".contentprovider.PebbleContentProvider"
|
||||
android:authorities="com.getpebble.android.provider"
|
||||
android:exported="true" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.screenshot_provider"
|
||||
@ -498,6 +551,7 @@
|
||||
<data android:scheme="gadgetbridge" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".devices.qhybrid.ConfigActivity"
|
||||
android:exported="true" />
|
||||
@ -507,6 +561,8 @@
|
||||
<activity
|
||||
android:name=".devices.qhybrid.HRConfigActivity"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name=".devices.qhybrid.ImageEditActivity"
|
||||
android:exported="true" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -22,6 +22,7 @@ import android.app.Application;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.NotificationManager.Policy;
|
||||
import android.app.UiModeManager;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -42,6 +43,7 @@ import android.provider.ContactsContract.PhoneLookup;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import java.io.File;
|
||||
@ -81,16 +83,13 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITBIP;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITCOR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITCOR2;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.HPLUS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.ID115;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND2;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND3;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND4;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.ZETIME;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.fromKey;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_ID;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_ID;
|
||||
|
||||
/**
|
||||
* Main Application class that initializes and provides access to certain things like
|
||||
* logging and DB access.
|
||||
@ -107,6 +106,9 @@ public class GBApplication extends Application {
|
||||
private static final String PREFS_VERSION = "shared_preferences_version";
|
||||
//if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version
|
||||
private static final int CURRENT_PREFS_VERSION = 7;
|
||||
|
||||
private static final int ERROR_IN_GADGETBRIDGE_NOTIFICATION = 42;
|
||||
|
||||
private static LimitedQueue mIDSenderLookup = new LimitedQueue(16);
|
||||
private static Prefs prefs;
|
||||
private static GBPrefs gbPrefs;
|
||||
@ -211,7 +213,7 @@ public class GBApplication extends Application {
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
NotificationChannel channelHighPr = notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID );
|
||||
NotificationChannel channelHighPr = notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID);
|
||||
if (channelHighPr == null) {
|
||||
channelHighPr = new NotificationChannel(NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID,
|
||||
getString(R.string.notification_channel_high_priority_name),
|
||||
@ -222,7 +224,23 @@ public class GBApplication extends Application {
|
||||
bluetoothStateChangeReceiver = new BluetoothStateChangeReceiver();
|
||||
registerReceiver(bluetoothStateChangeReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
|
||||
}
|
||||
startService(new Intent(this, NotificationCollectorMonitorService.class));
|
||||
try {
|
||||
startService(new Intent(this, NotificationCollectorMonitorService.class));
|
||||
} catch (IllegalStateException e) {
|
||||
String message = e.toString();
|
||||
if (message == null) {
|
||||
message = getString(R.string._unknown_);
|
||||
}
|
||||
notificationManager.notify(ERROR_IN_GADGETBRIDGE_NOTIFICATION,
|
||||
new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
|
||||
.setSmallIcon(R.drawable.gadgetbridge_img)
|
||||
.setContentTitle(getString(R.string.error_background_service))
|
||||
.setContentText(getString(R.string.error_background_service_reason_truncated))
|
||||
.setStyle(new NotificationCompat.BigTextStyle()
|
||||
.bigText(getString(R.string.error_background_service_reason) + "\"" + message + "\""))
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -855,6 +873,9 @@ public class GBApplication extends Application {
|
||||
case MIBAND4:
|
||||
newWearside = prefs.getString("mi_wearside", "left");
|
||||
break;
|
||||
case MIBAND5:
|
||||
newWearside = prefs.getString("mi_wearside", "left");
|
||||
break;
|
||||
case HPLUS:
|
||||
newWearside = prefs.getString("hplus_wrist", "left");
|
||||
newTimeformat = prefs.getString("hplus_timeformat", "24h");
|
||||
@ -937,7 +958,13 @@ public class GBApplication extends Application {
|
||||
}
|
||||
|
||||
public static boolean isDarkThemeEnabled() {
|
||||
return prefs.getString("pref_key_theme", context.getString(R.string.pref_theme_value_light)).equals(context.getString(R.string.pref_theme_value_dark));
|
||||
String selectedTheme = prefs.getString("pref_key_theme", context.getString(R.string.pref_theme_value_system));
|
||||
|
||||
UiModeManager umm = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE);
|
||||
|
||||
return selectedTheme.equals(context.getString(R.string.pref_theme_value_dark)) ||
|
||||
(selectedTheme.equals(context.getString(R.string.pref_theme_value_system))
|
||||
&& (umm.getNightMode() == UiModeManager.MODE_NIGHT_YES));
|
||||
}
|
||||
|
||||
public static int getTextColor(Context context) {
|
||||
|
@ -0,0 +1,50 @@
|
||||
/* Copyright (C) 2015-2020 abettenburg, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Lem Dulfo
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
|
||||
|
||||
public class AboutActivity extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AboutActivity.class);
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_about);
|
||||
TextView link1 = (TextView) findViewById(R.id.links1);
|
||||
link1.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
TextView link2 = (TextView) findViewById(R.id.links2);
|
||||
link2.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
TextView link3 = (TextView) findViewById(R.id.links3);
|
||||
link3.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -35,6 +35,10 @@ public abstract class AbstractListActivity<T> extends AbstractGBActivity {
|
||||
this.itemAdapter.loadItems();
|
||||
}
|
||||
|
||||
public void setActivityKindFilter(int activityKind){
|
||||
this.itemAdapter.setActivityKindFilter(activityKind);
|
||||
}
|
||||
|
||||
public AbstractItemAdapter<T> getItemAdapter() {
|
||||
return itemAdapter;
|
||||
}
|
||||
|
@ -32,8 +32,10 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.DatePicker;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
@ -46,6 +48,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@ -54,15 +57,22 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
|
||||
public class ActivitySummariesActivity extends AbstractListActivity<BaseActivitySummary> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummariesActivity.class);
|
||||
private GBDevice mGBDevice;
|
||||
private SwipeRefreshLayout swipeLayout;
|
||||
LinkedHashMap<String , Integer> activityKindMap = new LinkedHashMap<>(1);
|
||||
int activityFilter=0;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
@ -121,7 +131,8 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setItemAdapter(new ActivitySummariesAdapter(this, mGBDevice));
|
||||
|
||||
setItemAdapter(new ActivitySummariesAdapter(this, mGBDevice,activityFilter));
|
||||
|
||||
getItemListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
@ -129,13 +140,12 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
Object item = parent.getItemAtPosition(position);
|
||||
if (item != null) {
|
||||
ActivitySummary summary = (ActivitySummary) item;
|
||||
|
||||
String gpxTrack = summary.getGpxTrack();
|
||||
if (gpxTrack != null) {
|
||||
showTrack(gpxTrack);
|
||||
} else {
|
||||
GB.toast("This activity does not contain GPX tracks.", Toast.LENGTH_LONG, GB.INFO);
|
||||
try {
|
||||
showActivityDetail(position);
|
||||
} catch (Exception e) {
|
||||
GB.toast(getApplicationContext(), "Unable to display Activity Detail, maybe the activity is not available yet: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -230,6 +240,52 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
fetchTrackData();
|
||||
}
|
||||
});
|
||||
|
||||
activityKindMap = fillKindMap();
|
||||
addItemsOnSpinner();
|
||||
addListenerOnSpinnerItemSelection();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private LinkedHashMap fillKindMap(){
|
||||
LinkedHashMap<String , Integer> newMap = new LinkedHashMap<>(1); //reset
|
||||
newMap.put("All Activities", 0);
|
||||
for (BaseActivitySummary item : getItemAdapter().getItems()) {
|
||||
String activityName = ActivityKind.asString(item.getActivityKind(), this);
|
||||
if (!newMap.containsKey(item.getActivityKind())) {
|
||||
newMap.put(activityName, item.getActivityKind());
|
||||
}
|
||||
}
|
||||
return newMap;
|
||||
}
|
||||
|
||||
public void addListenerOnSpinnerItemSelection() {
|
||||
Spinner spinner = (Spinner) findViewById(R.id.select_kind);
|
||||
spinner.setOnItemSelectedListener(new CustomOnItemSelectedListener());
|
||||
}
|
||||
|
||||
public class CustomOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
|
||||
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int pos,long id) {
|
||||
activityFilter=activityKindMap.get(parent.getItemAtPosition(pos));
|
||||
setActivityKindFilter(activityFilter);
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void addItemsOnSpinner() {
|
||||
Spinner spinner = (Spinner) findViewById(R.id.select_kind);
|
||||
ArrayList<String> spinnerArray = new ArrayList<>(activityKindMap.keySet());
|
||||
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_dropdown_item, spinnerArray);
|
||||
spinner.setAdapter(dataAdapter);
|
||||
}
|
||||
|
||||
public void resetFetchTimestampToChosenDate() {
|
||||
@ -263,12 +319,13 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
refresh();
|
||||
}
|
||||
|
||||
private void showTrack(String gpxTrack) {
|
||||
try {
|
||||
AndroidUtils.viewFile(gpxTrack, Intent.ACTION_VIEW, this);
|
||||
} catch (IOException e) {
|
||||
GB.toast(this, "Unable to display GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
private void showActivityDetail(int position){
|
||||
Intent ActivitySummaryDetailIntent = new Intent(this, ActivitySummaryDetail.class);
|
||||
ActivitySummaryDetailIntent.putExtra("position", position);
|
||||
ActivitySummaryDetailIntent.putExtra("filter", activityFilter);
|
||||
ActivitySummaryDetailIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice);
|
||||
startActivity(ActivitySummaryDetailIntent);
|
||||
|
||||
}
|
||||
|
||||
private void fetchTrackData() {
|
||||
@ -300,4 +357,7 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,380 @@
|
||||
/* Copyright (C) 2015-2020 abettenburg, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Lem Dulfo
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryItems;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.SwipeEvents;
|
||||
|
||||
public class ActivitySummaryDetail extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryDetail.class);
|
||||
private GBDevice mGBDevice;
|
||||
private JSONObject groupData = setGroups();
|
||||
private boolean show_raw_data = false;
|
||||
BaseActivitySummary currentItem = null;
|
||||
private int alternateColor;
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_summary_details);
|
||||
Intent intent = getIntent();
|
||||
mGBDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
final int filter = intent.getIntExtra("filter",0);
|
||||
final int position = intent.getIntExtra("position",0);
|
||||
final ActivitySummaryItems items = new ActivitySummaryItems(this, mGBDevice, filter);
|
||||
final RelativeLayout layout = findViewById(R.id.activity_summary_detail_relative_layout);
|
||||
alternateColor = getAlternateColor(this);
|
||||
|
||||
final Animation animFadeRight;
|
||||
final Animation animFadeLeft;
|
||||
final Animation animBounceLeft;
|
||||
final Animation animBounceRight;
|
||||
|
||||
animFadeRight = AnimationUtils.loadAnimation(
|
||||
this,
|
||||
R.anim.flyright);
|
||||
animFadeLeft = AnimationUtils.loadAnimation(
|
||||
this,
|
||||
R.anim.flyleft);
|
||||
animBounceLeft = AnimationUtils.loadAnimation(
|
||||
this,
|
||||
R.anim.bounceleft);
|
||||
animBounceRight = AnimationUtils.loadAnimation(
|
||||
this,
|
||||
R.anim.bounceright);
|
||||
|
||||
layout.setOnTouchListener(new SwipeEvents(this) {
|
||||
@Override
|
||||
public void onSwipeRight() {
|
||||
currentItem = items.getNextItem();
|
||||
if (currentItem != null) {
|
||||
makeSummaryHeader(currentItem);
|
||||
makeSummaryContent(currentItem);
|
||||
layout.startAnimation(animFadeRight);
|
||||
|
||||
}else{
|
||||
layout.startAnimation(animBounceRight);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onSwipeLeft() {
|
||||
currentItem = items.getPrevItem();
|
||||
if (currentItem != null) {
|
||||
makeSummaryHeader(currentItem);
|
||||
makeSummaryContent(currentItem);
|
||||
layout.startAnimation(animFadeLeft);
|
||||
}else{
|
||||
layout.startAnimation(animBounceLeft);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
currentItem = items.getItem(position);
|
||||
if (currentItem != null) {
|
||||
makeSummaryHeader(currentItem);
|
||||
makeSummaryContent(currentItem);
|
||||
}
|
||||
|
||||
//allows long-press.switch of data being in raw form or recalculated
|
||||
ImageView activity_icon = (ImageView) findViewById(R.id.item_image);
|
||||
activity_icon.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
public boolean onLongClick(View v) {
|
||||
show_raw_data=!show_raw_data;
|
||||
if (currentItem != null) {
|
||||
makeSummaryHeader(currentItem);
|
||||
makeSummaryContent(currentItem);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void makeSummaryHeader(BaseActivitySummary item){
|
||||
//make view of data from main part of item
|
||||
final String gpxTrack = item.getGpxTrack();
|
||||
Button show_track_btn = (Button) findViewById(R.id.showTrack);
|
||||
show_track_btn.setVisibility(View.GONE);
|
||||
|
||||
if (gpxTrack != null) {
|
||||
show_track_btn.setVisibility(View.VISIBLE);
|
||||
show_track_btn.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
AndroidUtils.viewFile(gpxTrack, Intent.ACTION_VIEW, ActivitySummaryDetail.this);
|
||||
} catch (IOException e) {
|
||||
GB.toast(getApplicationContext(), "Unable to display GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
String activitykindname = ActivityKind.asString(item.getActivityKind(), getApplicationContext());
|
||||
Date starttime = (Date) item.getStartTime();
|
||||
Date endtime = (Date) item.getEndTime();
|
||||
String starttimeS = DateTimeUtils.formatDateTime(starttime);
|
||||
String endtimeS = DateTimeUtils.formatDateTime(endtime);
|
||||
String durationhms = DateTimeUtils.formatDurationHoursMinutes((endtime.getTime() - starttime.getTime()), TimeUnit.MILLISECONDS);
|
||||
|
||||
ImageView activity_icon = (ImageView) findViewById(R.id.item_image);
|
||||
activity_icon.setImageResource(ActivityKind.getIconId(item.getActivityKind()));
|
||||
|
||||
TextView activity_kind = (TextView) findViewById(R.id.activitykind);
|
||||
activity_kind.setText(activitykindname);
|
||||
TextView start_time = (TextView) findViewById(R.id.starttime);
|
||||
start_time.setText(starttimeS);
|
||||
TextView end_time = (TextView) findViewById(R.id.endtime);
|
||||
end_time.setText(endtimeS);
|
||||
TextView activity_duration = (TextView) findViewById(R.id.duration);
|
||||
activity_duration.setText(durationhms);
|
||||
|
||||
}
|
||||
|
||||
private void makeSummaryContent (BaseActivitySummary item){
|
||||
//make view of data from summaryData of item
|
||||
|
||||
TableLayout fieldLayout = findViewById(R.id.summaryDetails);
|
||||
fieldLayout.removeAllViews(); //remove old widgets
|
||||
|
||||
JSONObject summarySubdata = null;
|
||||
JSONObject data = null;
|
||||
|
||||
String sumData = item.getSummaryData();
|
||||
|
||||
if (sumData != null) {
|
||||
try {
|
||||
summarySubdata = new JSONObject(sumData);
|
||||
} catch (JSONException e) {
|
||||
LOG.error("SportsActivity", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (summarySubdata == null) return;
|
||||
data = makeSummaryList(summarySubdata); //make new list, grouped by groups
|
||||
|
||||
if (data == null) return;
|
||||
|
||||
Iterator<String> keys = data.keys();
|
||||
DecimalFormat df = new DecimalFormat("#.##");
|
||||
|
||||
while (keys.hasNext()) {
|
||||
String key = keys.next();
|
||||
try {
|
||||
LOG.error("SportsActivity:" + key + ": " + data.get(key) + "\n");
|
||||
JSONArray innerList = (JSONArray) data.get(key);
|
||||
|
||||
TableRow label_row = new TableRow(ActivitySummaryDetail.this);
|
||||
TextView label_field = new TextView(ActivitySummaryDetail.this);
|
||||
label_field.setTextSize(16);
|
||||
label_field.setTypeface(null, Typeface.BOLD);
|
||||
label_field.setText(String.format("%s", getStringResourceByName(key)));
|
||||
label_row.addView(label_field);
|
||||
fieldLayout.addView(label_row);
|
||||
|
||||
for (int i = 0; i < innerList.length(); i++) {
|
||||
JSONObject innerData = innerList.getJSONObject(i);
|
||||
double value = innerData.getDouble("value");
|
||||
String unit = innerData.getString("unit");
|
||||
String name = innerData.getString("name");
|
||||
|
||||
if (!show_raw_data) {
|
||||
//special casing here:
|
||||
switch (unit) {
|
||||
case "meters_second":
|
||||
value = value * 3.6;
|
||||
unit = "km_h";
|
||||
break;
|
||||
case "seconds_m":
|
||||
value = 3.6 / value;
|
||||
unit = "minutes_km";
|
||||
break;
|
||||
case "seconds_km":
|
||||
value = value / 60;
|
||||
unit = "minutes_km";
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
TableRow field_row = new TableRow(ActivitySummaryDetail.this);
|
||||
if (i % 2 == 0) field_row.setBackgroundColor(alternateColor);
|
||||
|
||||
TextView name_field = new TextView(ActivitySummaryDetail.this);
|
||||
TextView value_field = new TextView(ActivitySummaryDetail.this);
|
||||
name_field.setGravity(Gravity.START);
|
||||
value_field.setGravity(Gravity.END);
|
||||
|
||||
if (unit.equals("seconds") && !show_raw_data) { //rather then plain seconds, show formatted duration
|
||||
value_field.setText(DateTimeUtils.formatDurationHoursMinutes((long) value, TimeUnit.SECONDS));
|
||||
}else {
|
||||
value_field.setText(String.format("%s %s", df.format(value), getStringResourceByName(unit)));
|
||||
}
|
||||
|
||||
name_field.setText(getStringResourceByName(name));
|
||||
TableRow.LayoutParams params = new TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 1f);
|
||||
value_field.setLayoutParams(params);
|
||||
|
||||
field_row.addView(name_field);
|
||||
field_row.addView(value_field);
|
||||
fieldLayout.addView(field_row);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
LOG.error("SportsActivity", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject setGroups(){
|
||||
String groupDefinitions = "{'Strokes':['averageStrokeDistance','averageStrokesPerSecond','strokes'], " +
|
||||
"'Swimming':['swolfIndex','swimStyle'], " +
|
||||
"'Elevation':['ascentMeters','descentMeters','maxAltitude','minAltitude','ascentSeconds','descentSeconds','flatSeconds'], " +
|
||||
"'Speed':['maxSpeed','minPace','maxPace','averageKMPaceSeconds'], " +
|
||||
"'Activity':['distanceMeters','steps','activeSeconds','caloriesBurnt','totalStride'," +
|
||||
"'averageHR','averageStride'], " +
|
||||
"'Laps':['averageLapPace','laps']}";
|
||||
JSONObject data = null;
|
||||
try {
|
||||
data = new JSONObject(groupDefinitions);
|
||||
} catch (JSONException e) {
|
||||
LOG.error("SportsActivity", e);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private String getGroup(String searchItem) {
|
||||
String defaultGroup = "Activity";
|
||||
if (groupData == null) return defaultGroup;
|
||||
Iterator<String> keys = groupData.keys();
|
||||
while (keys.hasNext()) {
|
||||
String key = keys.next();
|
||||
try {
|
||||
JSONArray itemList = (JSONArray) groupData.get(key);
|
||||
for (int i = 0; i < itemList.length(); i++) {
|
||||
if (itemList.getString(i).equals(searchItem)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
LOG.error("SportsActivity", e);
|
||||
}
|
||||
}
|
||||
return defaultGroup;
|
||||
}
|
||||
|
||||
private JSONObject makeSummaryList(JSONObject summaryData){
|
||||
//make dictionary with data for each group
|
||||
JSONObject list = new JSONObject();
|
||||
Iterator<String> keys = summaryData.keys();
|
||||
LOG.error("SportsActivity JSON:" + summaryData + keys);
|
||||
|
||||
while (keys.hasNext()) {
|
||||
String key = keys.next();
|
||||
|
||||
try {
|
||||
LOG.error("SportsActivity:" + key + ": " + summaryData.get(key) + "\n");
|
||||
JSONObject innerData = (JSONObject) summaryData.get(key);
|
||||
Object value = innerData.get("value");
|
||||
String unit = innerData.getString("unit");
|
||||
String group = getGroup(key);
|
||||
|
||||
if (!list.has(group)) {
|
||||
list.put(group,new JSONArray());
|
||||
}
|
||||
|
||||
JSONArray tmpl = (JSONArray) list.get(group);
|
||||
JSONObject innernew = new JSONObject();
|
||||
innernew.put("name", key);
|
||||
innernew.put("value", value);
|
||||
innernew.put("unit", unit);
|
||||
tmpl.put(innernew);
|
||||
list.put(group, tmpl);
|
||||
} catch (JSONException e) {
|
||||
LOG.error("SportsActivity", e);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
public static int getAlternateColor(Context context) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
theme.resolveAttribute(R.attr.alternate_row_background, typedValue, true);
|
||||
return typedValue.data;
|
||||
}
|
||||
|
||||
private String getStringResourceByName(String aString) {
|
||||
String packageName = getPackageName();
|
||||
int resId = getResources().getIdentifier(aString, "string", packageName);
|
||||
if (resId==0){
|
||||
LOG.warn("SportsActivity " + "Missing string in strings:" + aString);
|
||||
return aString;
|
||||
}else{
|
||||
return getString(resId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
// back button
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
}
|
@ -22,6 +22,7 @@ import android.text.format.DateFormat;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.CheckedTextView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TimePicker;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -46,6 +47,8 @@ public class AlarmDetails extends AbstractGBActivity {
|
||||
private CheckedTextView cbFriday;
|
||||
private CheckedTextView cbSaturday;
|
||||
private CheckedTextView cbSunday;
|
||||
private EditText title;
|
||||
private EditText description;
|
||||
private GBDevice device;
|
||||
|
||||
@Override
|
||||
@ -56,6 +59,9 @@ public class AlarmDetails extends AbstractGBActivity {
|
||||
alarm = (Alarm) getIntent().getSerializableExtra(nodomain.freeyourgadget.gadgetbridge.model.Alarm.EXTRA_ALARM);
|
||||
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
|
||||
title = findViewById(R.id.alarm_title);
|
||||
description = findViewById(R.id.alarm_description);
|
||||
|
||||
timePicker = findViewById(R.id.alarm_time_picker);
|
||||
cbSmartWakeup = findViewById(R.id.alarm_cb_smart_wakeup);
|
||||
cbSnooze = findViewById(R.id.alarm_cb_snooze);
|
||||
@ -126,6 +132,12 @@ public class AlarmDetails extends AbstractGBActivity {
|
||||
int snoozeVisibility = supportsSnoozing() ? View.VISIBLE : View.GONE;
|
||||
cbSnooze.setVisibility(snoozeVisibility);
|
||||
|
||||
int descriptionVisibility = supportsDescription() ? View.VISIBLE : View.GONE;
|
||||
title.setVisibility(descriptionVisibility);
|
||||
title.setText(alarm.getTitle());
|
||||
description.setVisibility(descriptionVisibility);
|
||||
description.setText(alarm.getDescription());
|
||||
|
||||
cbMonday.setChecked(alarm.getRepetition(Alarm.ALARM_MON));
|
||||
cbTuesday.setChecked(alarm.getRepetition(Alarm.ALARM_TUE));
|
||||
cbWednesday.setChecked(alarm.getRepetition(Alarm.ALARM_WED));
|
||||
@ -144,6 +156,14 @@ public class AlarmDetails extends AbstractGBActivity {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean supportsDescription() {
|
||||
if (device != null) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
return coordinator.supportsAlarmDescription(device);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean supportsSnoozing() {
|
||||
if (device != null) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
@ -170,6 +190,8 @@ public class AlarmDetails extends AbstractGBActivity {
|
||||
alarm.setRepetition(repetitionMask);
|
||||
alarm.setHour(timePicker.getCurrentHour());
|
||||
alarm.setMinute(timePicker.getCurrentMinute());
|
||||
alarm.setTitle(title.getText().toString());
|
||||
alarm.setDescription(description.getText().toString());
|
||||
DBHelper.store(alarm);
|
||||
}
|
||||
|
||||
|
@ -146,7 +146,7 @@ public class ConfigureAlarms extends AbstractGBActivity {
|
||||
}
|
||||
|
||||
private Alarm createDefaultAlarm(@NonNull Device device, @NonNull User user, int position) {
|
||||
return new Alarm(device.getId(), user.getId(), position, false, false, false, 0, 6, 30, false);
|
||||
return new Alarm(device.getId(), user.getId(), position, false, false, false, 0, 6, 30, false, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -39,6 +40,7 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.GravityCompat;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
@ -50,9 +52,11 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import de.cketti.library.changelog.ChangeLog;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -68,23 +72,19 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
public class ControlCenterv2 extends AppCompatActivity
|
||||
implements NavigationView.OnNavigationItemSelectedListener, GBActivity {
|
||||
|
||||
public static final int MENU_REFRESH_CODE = 1;
|
||||
private static PhoneStateListener fakeStateListener;
|
||||
|
||||
//needed for KK compatibility
|
||||
static {
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||
}
|
||||
|
||||
private DeviceManager deviceManager;
|
||||
|
||||
private GBDeviceAdapterv2 mGBDeviceAdapter;
|
||||
private RecyclerView deviceListView;
|
||||
private FloatingActionButton fab;
|
||||
|
||||
private boolean isLanguageInvalid = false;
|
||||
|
||||
public static final int MENU_REFRESH_CODE=1;
|
||||
|
||||
private static PhoneStateListener fakeStateListener;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
@ -102,6 +102,7 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
};
|
||||
private boolean pesterWithPermissions = true;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -192,11 +193,16 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
* Ask for permission to intercept notifications on first run.
|
||||
*/
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (prefs.getBoolean("firstrun", true)) {
|
||||
prefs.getPreferences().edit().putBoolean("firstrun", false).apply();
|
||||
Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
|
||||
startActivity(enableIntent);
|
||||
pesterWithPermissions = prefs.getBoolean("permission_pestering", true);
|
||||
|
||||
Set<String> set = NotificationManagerCompat.getEnabledListenerPackages(this);
|
||||
if (pesterWithPermissions) {
|
||||
if (!set.contains(this.getPackageName())) { // If notification listener access hasn't been granted
|
||||
Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
|
||||
startActivity(enableIntent);
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
checkAndRequestPermissions();
|
||||
}
|
||||
@ -205,7 +211,7 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
if (cl.isFirstRun()) {
|
||||
try {
|
||||
cl.getLogDialog().show();
|
||||
} catch (Exception ignored){
|
||||
} catch (Exception ignored) {
|
||||
GB.toast(getBaseContext(), "Error showing Changelog", Toast.LENGTH_LONG, GB.ERROR);
|
||||
|
||||
}
|
||||
@ -297,6 +303,10 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
GB.toast(getBaseContext(), "Error showing Changelog", Toast.LENGTH_LONG, GB.ERROR);
|
||||
}
|
||||
return true;
|
||||
case R.id.about:
|
||||
Intent aboutIntent = new Intent(this, AboutActivity.class);
|
||||
startActivity(aboutIntent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -343,8 +353,6 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
wantedPermissions.add(Manifest.permission.READ_CONTACTS);
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_DENIED)
|
||||
wantedPermissions.add(Manifest.permission.CALL_PHONE);
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ANSWER_PHONE_CALLS) == PackageManager.PERMISSION_DENIED)
|
||||
wantedPermissions.add(Manifest.permission.ANSWER_PHONE_CALLS);
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_DENIED)
|
||||
wantedPermissions.add(Manifest.permission.READ_CALL_LOG);
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED)
|
||||
@ -361,14 +369,69 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
wantedPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)
|
||||
wantedPermissions.add(Manifest.permission.READ_CALENDAR);
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED)
|
||||
wantedPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_DENIED)
|
||||
wantedPermissions.add(Manifest.permission.ACCESS_COARSE_LOCATION);
|
||||
|
||||
try {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.MEDIA_CONTENT_CONTROL) == PackageManager.PERMISSION_DENIED)
|
||||
wantedPermissions.add(Manifest.permission.MEDIA_CONTENT_CONTROL);
|
||||
} catch (Exception ignored){
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
if (!wantedPermissions.isEmpty())
|
||||
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (pesterWithPermissions) {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ANSWER_PHONE_CALLS) == PackageManager.PERMISSION_DENIED) {
|
||||
wantedPermissions.add(Manifest.permission.ANSWER_PHONE_CALLS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_DENIED) {
|
||||
wantedPermissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
|
||||
}
|
||||
}
|
||||
|
||||
if (!wantedPermissions.isEmpty()) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
// If this is not the first run, we can rely on
|
||||
// shouldShowRequestPermissionRationale(String permission)
|
||||
// and ignore permissions that shouldn't or can't be requested again
|
||||
if (prefs.getBoolean("permissions_asked", false)) {
|
||||
// Don't request permissions that we shouldn't show a prompt for
|
||||
// e.g. permissions that are "Never" granted by the user or never granted by the system
|
||||
Set<String> shouldNotAsk = new HashSet<>();
|
||||
for (String wantedPermission : wantedPermissions) {
|
||||
if (!shouldShowRequestPermissionRationale(wantedPermission)) {
|
||||
shouldNotAsk.add(wantedPermission);
|
||||
}
|
||||
}
|
||||
wantedPermissions.removeAll(shouldNotAsk);
|
||||
} else {
|
||||
// Permissions have not been asked yet, but now will be
|
||||
prefs.getPreferences().edit().putBoolean("permissions_asked", true).apply();
|
||||
}
|
||||
|
||||
if (!wantedPermissions.isEmpty()) {
|
||||
GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR);
|
||||
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0);
|
||||
GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/* In order to be able to set ringer mode to silent in GB's PhoneCallReceiver
|
||||
the permission to access notifications is needed above Android M
|
||||
ACCESS_NOTIFICATION_POLICY is also needed in the manifest */
|
||||
if (pesterWithPermissions) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (!((NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE)).isNotificationPolicyAccessGranted()) {
|
||||
GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR);
|
||||
startActivity(new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: On Lineage we have to do this so that the permission dialog pops up
|
||||
if (fakeStateListener == null) {
|
||||
|
@ -17,7 +17,6 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@ -160,9 +159,8 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
|
||||
}
|
||||
}
|
||||
catch (Exception fdfsdfds) {
|
||||
LOG.warn("fuck");
|
||||
} catch (Exception fdfsdfds) {
|
||||
LOG.error("Error", fdfsdfds);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
@ -205,7 +203,7 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
GB.toast("Error exporting device specific preferences", Toast.LENGTH_SHORT, GB.ERROR);
|
||||
GB.toast("Error exporting device specific preferences", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,6 +215,7 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
} catch (Exception ex) {
|
||||
GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_error_importing_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
|
||||
try (DBHandler lockHandler = GBApplication.acquireDB()) {
|
||||
List<Device> activeDevices = DBHelper.getActiveDevices(lockHandler.getDaoSession());
|
||||
for (Device dbDevice : activeDevices) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -46,18 +46,20 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.ConfigActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
@ -392,8 +394,14 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
newValues[0] = "default";
|
||||
|
||||
int i = 1;
|
||||
Set<String> existingNames = new HashSet<>();
|
||||
for (ResolveInfo resolveInfo : mediaReceivers) {
|
||||
newEntries[i] = resolveInfo.activityInfo.loadLabel(pm);
|
||||
newEntries[i] = resolveInfo.activityInfo.loadLabel(pm) + " (" + resolveInfo.activityInfo.packageName + ")";
|
||||
if (existingNames.contains(newEntries[i].toString().trim())) {
|
||||
newEntries[i] = resolveInfo.activityInfo.loadLabel(pm) + " (" + resolveInfo.activityInfo.name + ")";
|
||||
} else {
|
||||
existingNames.add(newEntries[i].toString().trim());
|
||||
}
|
||||
newValues[i] = resolveInfo.activityInfo.packageName;
|
||||
i++;
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
|
||||
public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class);
|
||||
protected final int TOTAL_DAYS = getRangeDays();
|
||||
protected int TOTAL_DAYS_FOR_AVERAGE = 0;
|
||||
|
||||
private Locale mLocale;
|
||||
private int mTargetValue = 0;
|
||||
@ -124,10 +125,17 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
ArrayList<String> labels = new ArrayList<String>();
|
||||
|
||||
long balance = 0;
|
||||
long daily_balance=0;
|
||||
TOTAL_DAYS_FOR_AVERAGE=0;
|
||||
|
||||
for (int counter = 0; counter < TOTAL_DAYS; counter++) {
|
||||
ActivityAmounts amounts = getActivityAmountsForDay(db, day, device);
|
||||
daily_balance=calculateBalance(amounts);
|
||||
if (daily_balance>0){
|
||||
TOTAL_DAYS_FOR_AVERAGE++;
|
||||
}
|
||||
|
||||
balance += calculateBalance(amounts);
|
||||
balance += daily_balance;
|
||||
entries.add(new BarEntry(counter, getTotalsForActivityAmounts(amounts)));
|
||||
labels.add(getWeeksChartsLabel(day));
|
||||
day.add(Calendar.DATE, 1);
|
||||
@ -146,8 +154,8 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
barChart.getAxisLeft().addLimitLine(target);
|
||||
|
||||
float average = 0;
|
||||
if (TOTAL_DAYS > 0) {
|
||||
average = Math.abs(balance / TOTAL_DAYS);
|
||||
if (TOTAL_DAYS_FOR_AVERAGE > 0) {
|
||||
average = Math.abs(balance / TOTAL_DAYS_FOR_AVERAGE);
|
||||
}
|
||||
LimitLine average_line = new LimitLine(average);
|
||||
average_line.setLabel(getString(R.string.average, getAverage(average)));
|
||||
|
@ -76,7 +76,7 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
||||
@Override
|
||||
protected String getBalanceMessage(long balance, int targetValue) {
|
||||
if (balance > 0) {
|
||||
final long totalBalance = balance - ((long)targetValue * TOTAL_DAYS);
|
||||
final long totalBalance = balance - ((long)targetValue * TOTAL_DAYS_FOR_AVERAGE);
|
||||
if (totalBalance > 0)
|
||||
return getString(R.string.overslept, getHM(totalBalance));
|
||||
else
|
||||
|
@ -109,7 +109,7 @@ public class WeekStepsChartFragment extends AbstractWeekChartFragment {
|
||||
@Override
|
||||
protected String getBalanceMessage(long balance, int targetValue) {
|
||||
if (balance > 0) {
|
||||
final long totalBalance = balance - ((long)targetValue * TOTAL_DAYS);
|
||||
final long totalBalance = balance - ((long)targetValue * TOTAL_DAYS_FOR_AVERAGE);
|
||||
if (totalBalance > 0)
|
||||
return getString(R.string.overstep, Math.abs(totalBalance));
|
||||
else
|
||||
|
@ -17,6 +17,7 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
|
||||
|
||||
public class DeviceSettingsPreferenceConst {
|
||||
public static final String PREF_LANGUAGE = "language";
|
||||
public static final String PREF_DATEFORMAT = "dateformat";
|
||||
public static final String PREF_TIMEFORMAT = "timeformat";
|
||||
public static final String PREF_WEARLOCATION = "wearlocation";
|
||||
@ -30,4 +31,18 @@ public class DeviceSettingsPreferenceConst {
|
||||
public static final String PREF_BUTTON_3_FUNCTION = "button_3_function";
|
||||
public static final String PREF_VIBRATION_STRENGH_PERCENTAGE = "vibration_strength";
|
||||
public static final String PREF_RELAX_FIRMWARE_CHECKS = "relax_firmware_checks";
|
||||
|
||||
public static final String PREF_HYBRID_HR_FORCE_WHITE_COLOR = "force_white_color_scheme";
|
||||
public static final String PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES = "widget_draw_circles";
|
||||
public static final String PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES = "save_raw_activity_files";
|
||||
|
||||
public static final String PREF_LIFTWRIST_NOSHED = "activate_display_on_lift_wrist_noshed";
|
||||
public static final String PREF_DISCONNECTNOTIF_NOSHED = "disconnect_notification_noshed";
|
||||
public static final String PREF_POWER_MODE = "power_mode";
|
||||
public static final String PREF_BUTTON_BP_CALIBRATE = "prefs_sensors_button_bp_calibration";
|
||||
public static final String PREF_ALTITUDE_CALIBRATE = "pref_sensors_altitude";
|
||||
public static final String PREF_LONGSIT_PERIOD = "pref_longsit_period";
|
||||
public static final String PREF_LONGSIT_SWITCH = "pref_longsit_switch";
|
||||
public static final String PREF_LONGSIT_SWITCH_NOSHED = "screen_longsit_noshed";
|
||||
public static final String PREF_DO_NOT_DISTURB_NOAUTO = "do_not_disturb_no_auto";
|
||||
}
|
@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
@ -39,10 +40,22 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreferenceFragment;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALTITUDE_CALIBRATE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_BP_CALIBRATE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_FORCE_WHITE_COLOR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LONGSIT_PERIOD;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LONGSIT_SWITCH;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_POWER_MODE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SCREEN_ORIENTATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE;
|
||||
@ -55,7 +68,15 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ON_LIFT_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ON_LIFT_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_EXPOSE_HR_THIRDPARTY;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_LANGUAGE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_SHORTCUTS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_FELL_SLEEP_SELECTION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_SELECTION_OFF;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_SELECTION_BROADCAST;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_START_NON_WEAR_BROADCAST;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_START_NON_WEAR_SELECTION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_WOKE_UP_BROADCAST;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_WOKE_UP_SELECTION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB_OFF;
|
||||
@ -312,6 +333,7 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat {
|
||||
addPreferenceHandlerFor(PREF_MI2_DATEFORMAT);
|
||||
addPreferenceHandlerFor(PREF_DATEFORMAT);
|
||||
addPreferenceHandlerFor(PREF_DISPLAY_ITEMS);
|
||||
addPreferenceHandlerFor(PREF_SHORTCUTS);
|
||||
addPreferenceHandlerFor(PREF_LANGUAGE);
|
||||
addPreferenceHandlerFor(PREF_EXPOSE_HR_THIRDPARTY);
|
||||
addPreferenceHandlerFor(PREF_WEARLOCATION);
|
||||
@ -321,6 +343,19 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat {
|
||||
addPreferenceHandlerFor(PREF_BUTTON_2_FUNCTION);
|
||||
addPreferenceHandlerFor(PREF_BUTTON_3_FUNCTION);
|
||||
addPreferenceHandlerFor(PREF_VIBRATION_STRENGH_PERCENTAGE);
|
||||
addPreferenceHandlerFor(PREF_POWER_MODE);
|
||||
addPreferenceHandlerFor(PREF_LIFTWRIST_NOSHED);
|
||||
addPreferenceHandlerFor(PREF_DISCONNECTNOTIF_NOSHED);
|
||||
addPreferenceHandlerFor(PREF_BUTTON_BP_CALIBRATE);
|
||||
addPreferenceHandlerFor(PREF_ALTITUDE_CALIBRATE);
|
||||
addPreferenceHandlerFor(PREF_LONGSIT_PERIOD);
|
||||
addPreferenceHandlerFor(PREF_LONGSIT_SWITCH);
|
||||
addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_NOAUTO);
|
||||
|
||||
addPreferenceHandlerFor(PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES);
|
||||
addPreferenceHandlerFor(PREF_HYBRID_HR_FORCE_WHITE_COLOR);
|
||||
addPreferenceHandlerFor(PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES);
|
||||
|
||||
|
||||
String displayOnLiftState = prefs.getString(PREF_ACTIVATE_DISPLAY_ON_LIFT, PREF_DO_NOT_DISTURB_OFF);
|
||||
boolean displayOnLiftScheduled = displayOnLiftState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
|
||||
@ -404,6 +439,94 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat {
|
||||
setInputTypeFor(MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS, InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
setInputTypeFor(MakibesHR3Constants.PREF_FIND_PHONE_DURATION, InputType.TYPE_CLASS_NUMBER);
|
||||
setInputTypeFor(DeviceSettingsPreferenceConst.PREF_RESERVER_ALARMS_CALENDAR, InputType.TYPE_CLASS_NUMBER);
|
||||
|
||||
String deviceActionsFellSleepSelection = prefs.getString(PREF_DEVICE_ACTION_FELL_SLEEP_SELECTION, PREF_DEVICE_ACTION_SELECTION_OFF);
|
||||
final String deviceActionsFellSleepBroadcastValue = prefs.getString(PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST,
|
||||
this.getContext().getString(R.string.prefs_events_forwarding_fellsleep_broadcast_default_value));
|
||||
|
||||
final Preference deviceActionsFellSleep = findPreference(PREF_DEVICE_ACTION_FELL_SLEEP_SELECTION);
|
||||
final Preference deviceActionsFellSleepBroadcast = findPreference(PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST);
|
||||
boolean deviceActionsFellSleepSelectionBroadcast = deviceActionsFellSleepSelection.equals(PREF_DEVICE_ACTION_SELECTION_BROADCAST);
|
||||
if (deviceActionsFellSleep != null) {
|
||||
deviceActionsFellSleep.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean broadcast = PREF_DEVICE_ACTION_SELECTION_BROADCAST.equals(newVal.toString());
|
||||
Objects.requireNonNull(deviceActionsFellSleepBroadcast).setEnabled(broadcast);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (deviceActionsFellSleepBroadcast != null) {
|
||||
deviceActionsFellSleepBroadcast.setSummary(deviceActionsFellSleepBroadcastValue);
|
||||
deviceActionsFellSleepBroadcast.setEnabled(deviceActionsFellSleepSelectionBroadcast);
|
||||
deviceActionsFellSleepBroadcast.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
deviceActionsFellSleepBroadcast.setSummary(newVal.toString());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String deviceActionsWokeUpSelection = prefs.getString(PREF_DEVICE_ACTION_WOKE_UP_SELECTION, PREF_DEVICE_ACTION_SELECTION_OFF);
|
||||
final String deviceActionsWokeUpBroadcastValue = prefs.getString(PREF_DEVICE_ACTION_WOKE_UP_BROADCAST,
|
||||
this.getContext().getString(R.string.prefs_events_forwarding_wokeup_broadcast_default_value));
|
||||
|
||||
final Preference deviceActionsWokeUp = findPreference(PREF_DEVICE_ACTION_WOKE_UP_SELECTION);
|
||||
final Preference deviceActionsWokeUpBroadcast = findPreference(PREF_DEVICE_ACTION_WOKE_UP_BROADCAST);
|
||||
boolean deviceActionsWokeUpSelectionBroadcast = deviceActionsWokeUpSelection.equals(PREF_DEVICE_ACTION_SELECTION_BROADCAST);
|
||||
if (deviceActionsWokeUp != null) {
|
||||
deviceActionsWokeUp.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean broadcast = PREF_DEVICE_ACTION_SELECTION_BROADCAST.equals(newVal.toString());
|
||||
Objects.requireNonNull(deviceActionsWokeUpBroadcast).setEnabled(broadcast);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (deviceActionsWokeUpBroadcast != null) {
|
||||
deviceActionsWokeUpBroadcast.setSummary(deviceActionsWokeUpBroadcastValue);
|
||||
deviceActionsWokeUpBroadcast.setEnabled(deviceActionsWokeUpSelectionBroadcast);
|
||||
deviceActionsWokeUpBroadcast.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
deviceActionsWokeUpBroadcast.setSummary(newVal.toString());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String deviceActionsStartNonWearSelection = prefs.getString(PREF_DEVICE_ACTION_START_NON_WEAR_SELECTION, PREF_DEVICE_ACTION_SELECTION_OFF);
|
||||
final String deviceActionsStartNonWearBroadcastValue = prefs.getString(PREF_DEVICE_ACTION_START_NON_WEAR_BROADCAST,
|
||||
this.getContext().getString(R.string.prefs_events_forwarding_startnonwear_broadcast_default_value));
|
||||
|
||||
final Preference deviceActionsStartNonWear = findPreference(PREF_DEVICE_ACTION_START_NON_WEAR_SELECTION);
|
||||
final Preference deviceActionsStartNonWearBroadcast = findPreference(PREF_DEVICE_ACTION_START_NON_WEAR_BROADCAST);
|
||||
boolean deviceActionsStartNonWearSelectionBroadcast = deviceActionsStartNonWearSelection.equals(PREF_DEVICE_ACTION_SELECTION_BROADCAST);
|
||||
if (deviceActionsStartNonWear != null) {
|
||||
deviceActionsStartNonWear.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean broadcast = PREF_DEVICE_ACTION_SELECTION_BROADCAST.equals(newVal.toString());
|
||||
Objects.requireNonNull(deviceActionsStartNonWearBroadcast).setEnabled(broadcast);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (deviceActionsStartNonWearBroadcast != null) {
|
||||
deviceActionsStartNonWearBroadcast.setSummary(deviceActionsStartNonWearBroadcastValue);
|
||||
deviceActionsStartNonWearBroadcast.setEnabled(deviceActionsStartNonWearSelectionBroadcast);
|
||||
deviceActionsStartNonWearBroadcast.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
deviceActionsStartNonWearBroadcast.setSummary(newVal.toString());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static DeviceSpecificSettingsFragment newInstance(String settingsFileSuffix, @NonNull int[] supportedSettings) {
|
||||
|
@ -59,6 +59,10 @@ public abstract class AbstractItemAdapter<T> extends ArrayAdapter<T> {
|
||||
this.horizontalAlignment = horizontalAlignment;
|
||||
}
|
||||
|
||||
public void setActivityKindFilter(int activityKind){
|
||||
this.setActivityKindFilter(activityKind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
T item = getItem(position);
|
||||
|
@ -21,6 +21,7 @@ import android.widget.Toast;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -36,10 +37,12 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class ActivitySummariesAdapter extends AbstractItemAdapter<BaseActivitySummary> {
|
||||
private final GBDevice device;
|
||||
private int activityKindFilter;
|
||||
|
||||
public ActivitySummariesAdapter(Context context, GBDevice device) {
|
||||
public ActivitySummariesAdapter(Context context, GBDevice device, int activityKindFilter) {
|
||||
super(context);
|
||||
this.device = device;
|
||||
this.activityKindFilter = activityKindFilter;
|
||||
loadItems();
|
||||
}
|
||||
|
||||
@ -50,7 +53,17 @@ public class ActivitySummariesAdapter extends AbstractItemAdapter<BaseActivitySu
|
||||
Device dbDevice = DBHelper.findDevice(device, handler.getDaoSession());
|
||||
|
||||
QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder();
|
||||
qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(dbDevice.getId())).orderDesc(BaseActivitySummaryDao.Properties.StartTime);
|
||||
if (activityKindFilter !=0){
|
||||
qb.where(
|
||||
BaseActivitySummaryDao.Properties.DeviceId.eq(dbDevice.getId()),
|
||||
BaseActivitySummaryDao.Properties.ActivityKind.eq(activityKindFilter))
|
||||
.orderDesc(BaseActivitySummaryDao.Properties.StartTime);
|
||||
}else{
|
||||
qb.where(
|
||||
BaseActivitySummaryDao.Properties.DeviceId.eq(
|
||||
dbDevice.getId())).orderDesc(BaseActivitySummaryDao.Properties.StartTime);
|
||||
}
|
||||
|
||||
List<BaseActivitySummary> allSummaries = qb.build().list();
|
||||
setItems(allSummaries, true);
|
||||
} catch (Exception e) {
|
||||
@ -58,23 +71,41 @@ public class ActivitySummariesAdapter extends AbstractItemAdapter<BaseActivitySu
|
||||
}
|
||||
}
|
||||
|
||||
public void setActivityKindFilter(int filter){
|
||||
this.activityKindFilter=filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getName(BaseActivitySummary item) {
|
||||
|
||||
|
||||
|
||||
|
||||
String name = item.getName();
|
||||
if (name != null && name.length() > 0) {
|
||||
return name;
|
||||
}
|
||||
|
||||
Date startTime = item.getStartTime();
|
||||
Long duration = (item.getEndTime().getTime() - item.getStartTime().getTime());
|
||||
|
||||
if (startTime != null) {
|
||||
return DateTimeUtils.formatDateTime(startTime);
|
||||
return DateTimeUtils.formatDateTime(startTime) + " (" + DateTimeUtils.formatDurationHoursMinutes(duration, TimeUnit.MILLISECONDS) + ")";
|
||||
}
|
||||
|
||||
|
||||
|
||||
return "Unknown activity";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDetails(BaseActivitySummary item) {
|
||||
return ActivityKind.asString(item.getActivityKind(), getContext());
|
||||
String gpxTrack = item.getGpxTrack();
|
||||
String hasGps = "";
|
||||
if (gpxTrack != null) {
|
||||
hasGps=" 🛰️";
|
||||
}
|
||||
return ActivityKind.asString(item.getActivityKind(), getContext())+ hasGps;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -16,6 +16,7 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.adapter;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -26,9 +27,12 @@ import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
@ -47,22 +51,37 @@ public class DeviceCandidateAdapter extends ArrayAdapter<GBDeviceCandidate> {
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
GBDeviceCandidate device = getItem(position);
|
||||
|
||||
if (view == null) {
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = inflater.inflate(R.layout.item_with_details, parent, false);
|
||||
}
|
||||
ImageView deviceImageView = (ImageView) view.findViewById(R.id.item_image);
|
||||
TextView deviceNameLabel = (TextView) view.findViewById(R.id.item_name);
|
||||
TextView deviceAddressLabel = (TextView) view.findViewById(R.id.item_details);
|
||||
ImageView deviceImageView = view.findViewById(R.id.item_image);
|
||||
TextView deviceNameLabel = view.findViewById(R.id.item_name);
|
||||
TextView deviceAddressLabel = view.findViewById(R.id.item_details);
|
||||
TextView deviceStatus = view.findViewById(R.id.item_status);
|
||||
|
||||
String name = formatDeviceCandidate(device);
|
||||
deviceNameLabel.setText(name);
|
||||
deviceAddressLabel.setText(device.getMacAddress());
|
||||
deviceImageView.setImageResource(device.getDeviceType().getIcon());
|
||||
|
||||
String status = "";
|
||||
if (device.getDevice().getBondState() == BluetoothDevice.BOND_BONDED) {
|
||||
status += getContext().getString(R.string.device_is_currently_bonded);
|
||||
if (!GBApplication.getPrefs().getBoolean("ignore_bonded_devices", true)) { // This could be passed to the constructor instead
|
||||
deviceImageView.setImageResource(device.getDeviceType().getDisabledIcon());
|
||||
}
|
||||
}
|
||||
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_REQUIRE_KEY) {
|
||||
if (device.getDevice().getBondState() == BluetoothDevice.BOND_BONDED) {
|
||||
status += "\n";
|
||||
}
|
||||
status += getContext().getString(R.string.device_requires_key);
|
||||
}
|
||||
|
||||
deviceStatus.setText(status);
|
||||
return view;
|
||||
}
|
||||
|
||||
|
@ -38,17 +38,21 @@ import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesActivity;
|
||||
@ -56,9 +60,12 @@ import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9CalibrationActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
@ -70,6 +77,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
* Adapter for displaying GBDevice instances.
|
||||
*/
|
||||
public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.ViewHolder> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceAdapterv2.class);
|
||||
|
||||
private final Context context;
|
||||
private List<GBDevice> deviceList;
|
||||
@ -319,11 +327,11 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
|
||||
|
||||
);
|
||||
|
||||
holder.calibrateDevice.setVisibility(device.isInitialized() && device.getType() == DeviceType.WATCH9 ? View.VISIBLE : View.GONE);
|
||||
holder.calibrateDevice.setVisibility(device.isInitialized() && (coordinator.getCalibrationActivity() != null) ? View.VISIBLE : View.GONE);
|
||||
holder.calibrateDevice.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent startIntent = new Intent(context, Watch9CalibrationActivity.class);
|
||||
Intent startIntent = new Intent(context, coordinator.getCalibrationActivity());
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
context.startActivity(startIntent);
|
||||
}
|
||||
@ -352,9 +360,9 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
float frequency = Float.valueOf(input.getText().toString());
|
||||
float frequency = Float.parseFloat(input.getText().toString());
|
||||
// Trim to 1 decimal place, discard the rest
|
||||
frequency = Float.valueOf(String.format(Locale.getDefault(), "%.1f", frequency));
|
||||
frequency = Float.parseFloat(String.format(Locale.getDefault(), "%.1f", frequency));
|
||||
if (frequency < 87.5 || frequency > 108.0) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.pref_invalid_frequency_title)
|
||||
@ -468,6 +476,47 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
|
||||
}
|
||||
});
|
||||
|
||||
//set alias, hidden under details
|
||||
holder.setAlias.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final EditText input = new EditText(context);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
input.setText(device.getAlias());
|
||||
// Specify the type of input expected; this, for example, sets the input as a password, and will mask the text
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setView(input)
|
||||
.setCancelable(true)
|
||||
.setTitle(context.getString(R.string.controlcenter_set_alias))
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
DaoSession session = dbHandler.getDaoSession();
|
||||
Device dbDevice = DBHelper.getDevice(device, session);
|
||||
String alias = input.getText().toString();
|
||||
dbDevice.setAlias(alias);
|
||||
dbDevice.update();
|
||||
device.setAlias(alias);
|
||||
} catch (Exception ex) {
|
||||
GB.toast(context, context.getString(R.string.error_setting_alias) + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
} finally {
|
||||
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// do nothing
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -504,6 +553,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
|
||||
ListView deviceInfoList;
|
||||
ImageView findDevice;
|
||||
ImageView removeDevice;
|
||||
ImageView setAlias;
|
||||
LinearLayout fmFrequencyBox;
|
||||
TextView fmFrequencyLabel;
|
||||
ImageView ledColor;
|
||||
@ -537,6 +587,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
|
||||
deviceInfoList = view.findViewById(R.id.device_item_infos);
|
||||
findDevice = view.findViewById(R.id.device_action_find);
|
||||
removeDevice = view.findViewById(R.id.device_action_remove);
|
||||
setAlias = view.findViewById(R.id.device_action_set_alias);
|
||||
fmFrequencyBox = view.findViewById(R.id.device_fm_frequency_box);
|
||||
fmFrequencyLabel = view.findViewById(R.id.fm_frequency);
|
||||
ledColor = view.findViewById(R.id.device_led_color);
|
||||
@ -564,7 +615,8 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
|
||||
}
|
||||
|
||||
private String getUniqueDeviceName(GBDevice device) {
|
||||
String deviceName = device.getName();
|
||||
String deviceName = device.getAliasOrName();
|
||||
|
||||
if (!isUniqueDeviceName(device, deviceName)) {
|
||||
if (device.getModel() != null) {
|
||||
deviceName = deviceName + " " + device.getModel();
|
||||
|
@ -429,6 +429,7 @@ public class DBHelper {
|
||||
if (!isDeviceUpToDate(device, gbDevice)) {
|
||||
device.setIdentifier(gbDevice.getAddress());
|
||||
device.setName(gbDevice.getName());
|
||||
device.setAlias(gbDevice.getAlias());
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
|
||||
device.setManufacturer(coordinator.getManufacturer());
|
||||
device.setType(gbDevice.getType().getKey());
|
||||
@ -449,6 +450,9 @@ public class DBHelper {
|
||||
if (!Objects.equals(device.getName(), gbDevice.getName())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(device.getAlias(), gbDevice.getAlias())) {
|
||||
return false;
|
||||
}
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
|
||||
if (!Objects.equals(device.getManufacturer(), coordinator.getManufacturer())) {
|
||||
return false;
|
||||
|
@ -0,0 +1,43 @@
|
||||
/* Copyright (C) 2017-2020 Andreas Shimokawa, protomors
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.database.schema;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AlarmDao;
|
||||
|
||||
public class GadgetbridgeUpdate_26 implements DBUpdateScript {
|
||||
@Override
|
||||
public void upgradeSchema(SQLiteDatabase db) {
|
||||
if (!DBHelper.existsColumn(AlarmDao.TABLENAME, AlarmDao.Properties.Title.columnName, db)) {
|
||||
String ADD_COLUMN_TITLE = "ALTER TABLE " + AlarmDao.TABLENAME + " ADD COLUMN "
|
||||
+ AlarmDao.Properties.Title.columnName + " TEXT";
|
||||
db.execSQL(ADD_COLUMN_TITLE);
|
||||
}
|
||||
if (!DBHelper.existsColumn(AlarmDao.TABLENAME, AlarmDao.Properties.Description.columnName, db)) {
|
||||
String ADD_COLUMN_TITLE = "ALTER TABLE " + AlarmDao.TABLENAME + " ADD COLUMN "
|
||||
+ AlarmDao.Properties.Description.columnName + " TEXT";
|
||||
db.execSQL(ADD_COLUMN_TITLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downgradeSchema(SQLiteDatabase db) {
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/* Copyright (C) 2017-2020 Andreas Shimokawa, protomors
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.database.schema;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceDao;
|
||||
|
||||
public class GadgetbridgeUpdate_27 implements DBUpdateScript {
|
||||
@Override
|
||||
public void upgradeSchema(SQLiteDatabase db) {
|
||||
if (!DBHelper.existsColumn(DeviceDao.TABLENAME, DeviceDao.Properties.Alias.columnName, db)) {
|
||||
String ADD_COLUMN_ALIAS = "ALTER TABLE " + DeviceDao.TABLENAME + " ADD COLUMN "
|
||||
+ DeviceDao.Properties.Alias.columnName + " TEXT";
|
||||
db.execSQL(ADD_COLUMN_ALIAS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downgradeSchema(SQLiteDatabase db) {
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/* Copyright (C) 2017-2020 Andreas Shimokawa, protomors
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.database.schema;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
|
||||
|
||||
public class GadgetbridgeUpdate_29 implements DBUpdateScript {
|
||||
@Override
|
||||
public void upgradeSchema(SQLiteDatabase db) {
|
||||
if (!DBHelper.existsColumn(BaseActivitySummaryDao.TABLENAME, BaseActivitySummaryDao.Properties.SummaryData.columnName, db)) {
|
||||
String ADD_COLUMN_SUMMARY_DATA = "ALTER TABLE " + BaseActivitySummaryDao.TABLENAME + " ADD COLUMN "
|
||||
+ BaseActivitySummaryDao.Properties.SummaryData.columnName + " TEXT";
|
||||
db.execSQL(ADD_COLUMN_SUMMARY_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downgradeSchema(SQLiteDatabase db) {
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/* Copyright (C) 2017-2020 Andreas Shimokawa, protomors
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.database.schema;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
|
||||
|
||||
public class GadgetbridgeUpdate_30 implements DBUpdateScript {
|
||||
@Override
|
||||
public void upgradeSchema(SQLiteDatabase db) {
|
||||
if (!DBHelper.existsColumn(BaseActivitySummaryDao.TABLENAME, BaseActivitySummaryDao.Properties.RawSummaryData.columnName, db)) {
|
||||
String ADD_COLUMN_RAW_SUMMARY_DATA = "ALTER TABLE " + BaseActivitySummaryDao.TABLENAME + " ADD COLUMN "
|
||||
+ BaseActivitySummaryDao.Properties.RawSummaryData.columnName + " BLOB";
|
||||
db.execSQL(ADD_COLUMN_RAW_SUMMARY_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downgradeSchema(SQLiteDatabase db) {
|
||||
}
|
||||
}
|
@ -17,11 +17,13 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.bluetooth.BluetoothClass;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -35,6 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9PairingActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
|
||||
@ -65,7 +68,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public GBDevice createDevice(GBDeviceCandidate candidate) {
|
||||
return new GBDevice(candidate.getDevice().getAddress(), candidate.getName(), getDeviceType());
|
||||
return new GBDevice(candidate.getDevice().getAddress(), candidate.getName(), null, getDeviceType());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -152,6 +155,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAlarmDescription(GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMusicInfo() {
|
||||
return false;
|
||||
@ -180,4 +188,10 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends Activity> getCalibrationActivity() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -128,6 +128,9 @@ public interface DeviceCoordinator {
|
||||
@Nullable
|
||||
Class<? extends Activity> getPairingActivity();
|
||||
|
||||
@Nullable
|
||||
Class<? extends Activity> getCalibrationActivity();
|
||||
|
||||
/**
|
||||
* Returns true if activity data fetching is supported by the device
|
||||
* (with this coordinator).
|
||||
@ -207,6 +210,12 @@ public interface DeviceCoordinator {
|
||||
*/
|
||||
boolean supportsAlarmSnoozing();
|
||||
|
||||
/**
|
||||
* Returns true if this device/coordinator supports alarm descriptions
|
||||
* @return
|
||||
*/
|
||||
boolean supportsAlarmDescription(GBDevice device);
|
||||
|
||||
/**
|
||||
* Returns true if the given device supports heart rate measurements.
|
||||
* @return
|
||||
|
@ -49,6 +49,7 @@ public class HuamiConst {
|
||||
public static final String MI_BAND3_NAME = "Mi Band 3";
|
||||
public static final String MI_BAND3_NAME_2 = "Xiaomi Band 3";
|
||||
public static final String MI_BAND4_NAME = "Mi Smart Band 4";
|
||||
public static final String MI_BAND5_NAME = "Mi Smart Band 5";
|
||||
|
||||
public static final String PREF_ACTIVATE_DISPLAY_ON_LIFT = "activate_display_on_lift_wrist";
|
||||
public static final String PREF_DISPLAY_ON_LIFT_START = "display_on_lift_start";
|
||||
@ -59,7 +60,7 @@ public class HuamiConst {
|
||||
public static final String PREF_DISCONNECT_NOTIFICATION_END = "disconnect_notification_end";
|
||||
|
||||
public static final String PREF_DISPLAY_ITEMS = "display_items";
|
||||
public static final String PREF_LANGUAGE = "language";
|
||||
public static final String PREF_SHORTCUTS = "shortcuts";
|
||||
public static final String PREF_EXPOSE_HR_THIRDPARTY = "expose_hr_thirdparty";
|
||||
public static final String PREF_USE_CUSTOM_FONT = "use_custom_font";
|
||||
|
||||
@ -70,6 +71,16 @@ public class HuamiConst {
|
||||
public static final String PREF_BUTTON_ACTION_BROADCAST_DELAY = "button_action_broadcast_delay";
|
||||
public static final String PREF_BUTTON_ACTION_BROADCAST = "button_action_broadcast";
|
||||
|
||||
public static final String PREF_DEVICE_ACTION_SELECTION_OFF = "UNKNOWN";
|
||||
public static final String PREF_DEVICE_ACTION_SELECTION_BROADCAST = "BROADCAST";
|
||||
public static final String PREF_DEVICE_ACTION_FELL_SLEEP_SELECTION = "events_forwarding_fellsleep_action_selection";
|
||||
public static final String PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST = "prefs_events_forwarding_fellsleep_broadcast";
|
||||
public static final String PREF_DEVICE_ACTION_WOKE_UP_SELECTION = "events_forwarding_wokeup_action_selection";
|
||||
public static final String PREF_DEVICE_ACTION_WOKE_UP_BROADCAST = "prefs_events_forwarding_wokeup_broadcast";
|
||||
public static final String PREF_DEVICE_ACTION_START_NON_WEAR_SELECTION = "events_forwarding_startnonwear_action_selection";
|
||||
public static final String PREF_DEVICE_ACTION_START_NON_WEAR_BROADCAST = "prefs_events_forwarding_startnonwear_broadcast";
|
||||
|
||||
|
||||
public static int toActivityKind(int rawType) {
|
||||
switch (rawType) {
|
||||
case TYPE_DEEP_SLEEP:
|
||||
|
@ -35,7 +35,6 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -192,11 +191,6 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator {
|
||||
return getTimePreference(HuamiConst.PREF_DISCONNECT_NOTIFICATION_END, "00:00", deviceAddress);
|
||||
}
|
||||
|
||||
public static Set<String> getDisplayItems(String deviceAddress) {
|
||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
|
||||
return prefs.getStringSet(HuamiConst.PREF_DISPLAY_ITEMS, null);
|
||||
}
|
||||
|
||||
public static boolean getUseCustomFont(String deviceAddress) {
|
||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
|
||||
return prefs.getBoolean(HuamiConst.PREF_USE_CUSTOM_FONT, false);
|
||||
|
@ -28,6 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
|
||||
|
||||
public abstract class HuamiFWHelper extends AbstractMiBandFWHelper {
|
||||
protected HuamiFirmwareInfo firmwareInfo;
|
||||
@ -110,6 +111,10 @@ public abstract class HuamiFWHelper extends AbstractMiBandFWHelper {
|
||||
firmwareInfo.checkValid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HuamiFirmwareType getFirmwareType() {
|
||||
return firmwareInfo.getFirmwareType();
|
||||
}
|
||||
public HuamiFirmwareInfo getFirmwareInfo() {
|
||||
return firmwareInfo;
|
||||
}
|
||||
|
@ -89,6 +89,7 @@ public class AmazfitBipCoordinator extends HuamiCoordinator {
|
||||
R.xml.devicesettings_sync_calendar,
|
||||
R.xml.devicesettings_expose_hr_thirdparty,
|
||||
R.xml.devicesettings_buttonactions_with_longpress,
|
||||
R.xml.devicesettings_device_actions,
|
||||
R.xml.devicesettings_pairingkey
|
||||
};
|
||||
}
|
||||
|
@ -58,7 +58,8 @@ public class AmazfitBipSCoordinator extends HuamiCoordinator {
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return null;
|
||||
AmazfitBipSFWInstallHandler handler = new AmazfitBipSFWInstallHandler(uri, context);
|
||||
return handler.isValid() ? handler : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -76,18 +77,22 @@ public class AmazfitBipSCoordinator extends HuamiCoordinator {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMusicInfo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_amazfitbip,
|
||||
R.xml.devicesettings_amazfitbips,
|
||||
R.xml.devicesettings_timeformat,
|
||||
R.xml.devicesettings_wearlocation,
|
||||
R.xml.devicesettings_custom_emoji_font,
|
||||
R.xml.devicesettings_liftwrist_display,
|
||||
R.xml.devicesettings_disconnectnotification,
|
||||
R.xml.devicesettings_sync_calendar,
|
||||
R.xml.devicesettings_expose_hr_thirdparty,
|
||||
R.xml.devicesettings_buttonactions_with_longpress,
|
||||
R.xml.devicesettings_high_mtu,
|
||||
R.xml.devicesettings_pairingkey
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
/* Copyright (C) 2017-2020 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbips;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips.AmazfitBipSFirmwareInfo;
|
||||
|
||||
public class AmazfitBipSFWHelper extends HuamiFWHelper {
|
||||
|
||||
public AmazfitBipSFWHelper(Uri uri, Context context) throws IOException {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
|
||||
firmwareInfo = new AmazfitBipSFirmwareInfo(wholeFirmwareBytes);
|
||||
if (!firmwareInfo.isHeaderValid()) {
|
||||
throw new IllegalArgumentException("Not a an Amazfit Bip S firmware");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbips;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
class AmazfitBipSFWInstallHandler extends AbstractMiBandFWInstallHandler {
|
||||
AmazfitBipSFWInstallHandler(Uri uri, Context context) {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFwUpgradeNotice() {
|
||||
return mContext.getString(R.string.fw_upgrade_notice_amazfitbip, helper.getHumanFirmwareVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
|
||||
return new AmazfitBipSFWHelper(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedDeviceType(GBDevice device) {
|
||||
return device.getType() == DeviceType.AMAZFITBIPS;
|
||||
}
|
||||
}
|
@ -88,7 +88,7 @@ public class AmazfitGTRCoordinator extends HuamiCoordinator {
|
||||
}
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_amazfitgtr,
|
||||
R.xml.devicesettings_amazfitgtsgtr,
|
||||
R.xml.devicesettings_wearlocation,
|
||||
R.xml.devicesettings_timeformat,
|
||||
R.xml.devicesettings_liftwrist_display,
|
||||
|
@ -0,0 +1,102 @@
|
||||
/* Copyright (C) 2017-2020 Andreas Shimokawa, Daniele Gobbetti, João
|
||||
Paulo Barraca, José Rebelo, tiparega
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class AmazfitGTRLiteCoordinator extends HuamiCoordinator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmazfitGTRLiteCoordinator.class);
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.AMAZFITGTR_LITE;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
try {
|
||||
BluetoothDevice device = candidate.getDevice();
|
||||
String name = device.getName();
|
||||
if (name != null && name.equalsIgnoreCase("Amazfit GTR L")) {
|
||||
return DeviceType.AMAZFITGTR_LITE;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.error("unable to check device support", ex);
|
||||
}
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
AmazfitGTRLiteFWInstallHandler handler = new AmazfitGTRLiteFWInstallHandler(uri, context);
|
||||
return handler.isValid() ? handler : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle() {
|
||||
return BONDING_STYLE_REQUIRE_KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracks() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMusicInfo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_amazfitgtsgtr,
|
||||
R.xml.devicesettings_wearlocation,
|
||||
R.xml.devicesettings_timeformat,
|
||||
R.xml.devicesettings_liftwrist_display,
|
||||
R.xml.devicesettings_disconnectnotification,
|
||||
R.xml.devicesettings_sync_calendar,
|
||||
R.xml.devicesettings_expose_hr_thirdparty,
|
||||
R.xml.devicesettings_pairingkey
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgtr.AmazfitGTRLiteFirmwareInfo;
|
||||
|
||||
public class AmazfitGTRLiteFWHelper extends HuamiFWHelper {
|
||||
|
||||
public AmazfitGTRLiteFWHelper(Uri uri, Context context) throws IOException {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
|
||||
firmwareInfo = new AmazfitGTRLiteFirmwareInfo(wholeFirmwareBytes);
|
||||
if (!firmwareInfo.isHeaderValid()) {
|
||||
throw new IllegalArgumentException("Not a an Amazifit GTR Lite Firmware");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
class AmazfitGTRLiteFWInstallHandler extends AbstractMiBandFWInstallHandler {
|
||||
AmazfitGTRLiteFWInstallHandler(Uri uri, Context context) {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFwUpgradeNotice() {
|
||||
return mContext.getString(R.string.fw_upgrade_notice_amazfitgtr, helper.getHumanFirmwareVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
|
||||
return new AmazfitGTRLiteFWHelper(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedDeviceType(GBDevice device) {
|
||||
return device.getType() == DeviceType.AMAZFITGTR_LITE;
|
||||
}
|
||||
}
|
@ -88,7 +88,7 @@ public class AmazfitGTSCoordinator extends HuamiCoordinator {
|
||||
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_amazfitgtr,
|
||||
R.xml.devicesettings_amazfitgtsgtr,
|
||||
R.xml.devicesettings_wearlocation,
|
||||
R.xml.devicesettings_timeformat,
|
||||
R.xml.devicesettings_liftwrist_display,
|
||||
|
@ -22,7 +22,6 @@ import android.net.Uri;
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgtr.AmazfitGTRFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
@ -40,7 +39,7 @@ class AmazfitGTSFWInstallHandler extends AbstractMiBandFWInstallHandler {
|
||||
|
||||
@Override
|
||||
protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
|
||||
return new AmazfitGTRFWHelper(uri, context);
|
||||
return new AmazfitGTSFWHelper(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,101 @@
|
||||
/* Copyright (C) 2017-2020 Andreas Shimokawa, Daniele Gobbetti, João
|
||||
Paulo Barraca, José Rebelo, tiparega
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrex;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class AmazfitTRexCoordinator extends HuamiCoordinator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmazfitTRexCoordinator.class);
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.AMAZFITTREX;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
try {
|
||||
BluetoothDevice device = candidate.getDevice();
|
||||
String name = device.getName();
|
||||
if (name != null && name.equalsIgnoreCase("Amazfit T-Rex")) {
|
||||
return DeviceType.AMAZFITTREX;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.error("unable to check device support", ex);
|
||||
}
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
AmazfitTRexFWInstallHandler handler = new AmazfitTRexFWInstallHandler(uri, context);
|
||||
return handler.isValid() ? handler : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle() {
|
||||
return BONDING_STYLE_REQUIRE_KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracks() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMusicInfo() {
|
||||
return true;
|
||||
}
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_amazfitgtsgtr,
|
||||
R.xml.devicesettings_wearlocation,
|
||||
R.xml.devicesettings_timeformat,
|
||||
R.xml.devicesettings_liftwrist_display,
|
||||
R.xml.devicesettings_disconnectnotification,
|
||||
R.xml.devicesettings_sync_calendar,
|
||||
R.xml.devicesettings_expose_hr_thirdparty,
|
||||
R.xml.devicesettings_pairingkey
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrex;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfittrex.AmazfitTRexFirmwareInfo;
|
||||
|
||||
public class AmazfitTRexFWHelper extends HuamiFWHelper {
|
||||
|
||||
public AmazfitTRexFWHelper(Uri uri, Context context) throws IOException {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
|
||||
firmwareInfo = new AmazfitTRexFirmwareInfo(wholeFirmwareBytes);
|
||||
if (!firmwareInfo.isHeaderValid()) {
|
||||
throw new IllegalArgumentException("Not a an Amazifit T-Rex firmware");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfittrex;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
class AmazfitTRexFWInstallHandler extends AbstractMiBandFWInstallHandler {
|
||||
AmazfitTRexFWInstallHandler(Uri uri, Context context) {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFwUpgradeNotice() {
|
||||
return mContext.getString(R.string.fw_upgrade_notice_amazfit_trex, helper.getHumanFirmwareVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
|
||||
return new AmazfitTRexFWHelper(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedDeviceType(GBDevice device) {
|
||||
return device.getType() == DeviceType.AMAZFITTREX;
|
||||
}
|
||||
}
|
@ -67,8 +67,19 @@ public class MiBand2FWInstallHandler extends AbstractMiBandFWInstallHandler {
|
||||
Version v53 = MiBandConst.MI2_FW_VERSION_INTERMEDIATE_UPGRADE_53;
|
||||
if (deviceVersion.compareTo(v53) < 0) {
|
||||
String vInstall = getHelper().format(getHelper().getFirmwareVersion());
|
||||
if (vInstall == null || new Version(vInstall).compareTo(v53) > 0) {
|
||||
String newInfoText = getContext().getString(R.string.mi2_fw_installhandler_fw53_hint, v53.get()) + "\n\n" + installActivity.getInfoText();
|
||||
try {
|
||||
if (vInstall == null || new Version(vInstall).compareTo(v53) > 0) {
|
||||
String newInfoText = getContext().getString(R.string.mi2_fw_installhandler_fw53_hint, v53.get()) +
|
||||
"\n\n" +
|
||||
installActivity.getInfoText();
|
||||
installActivity.setInfoText(newInfoText);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
String newInfoText = getContext().getString(R.string.mi2_fw_installhandler_fw53_hint, v53.get()) +
|
||||
"\n\n" +
|
||||
installActivity.getInfoText() +
|
||||
"\n\n" +
|
||||
getContext().getString(R.string.error_version_check_extreme_caution, vInstall);
|
||||
installActivity.setInfoText(newInfoText);
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +112,7 @@ public class MiBand3Coordinator extends HuamiCoordinator {
|
||||
R.xml.devicesettings_swipeunlock,
|
||||
R.xml.devicesettings_sync_calendar,
|
||||
R.xml.devicesettings_expose_hr_thirdparty,
|
||||
R.xml.devicesettings_device_actions,
|
||||
R.xml.devicesettings_pairingkey
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,110 @@
|
||||
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, HardLight, José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband5;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class MiBand5Coordinator extends HuamiCoordinator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MiBand5Coordinator.class);
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.MIBAND5;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
try {
|
||||
BluetoothDevice device = candidate.getDevice();
|
||||
String name = device.getName();
|
||||
if (name != null && name.equalsIgnoreCase(HuamiConst.MI_BAND5_NAME)) {
|
||||
return DeviceType.MIBAND5;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.error("unable to check device support", ex);
|
||||
}
|
||||
return DeviceType.UNKNOWN;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
MiBand5FWInstallHandler handler = new MiBand5FWInstallHandler(uri, context);
|
||||
return handler.isValid() ? handler : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracks() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMusicInfo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_miband5,
|
||||
R.xml.devicesettings_wearlocation,
|
||||
R.xml.devicesettings_custom_emoji_font,
|
||||
R.xml.devicesettings_timeformat,
|
||||
R.xml.devicesettings_dateformat,
|
||||
R.xml.devicesettings_nightmode,
|
||||
R.xml.devicesettings_liftwrist_display,
|
||||
R.xml.devicesettings_swipeunlock,
|
||||
R.xml.devicesettings_sync_calendar,
|
||||
R.xml.devicesettings_expose_hr_thirdparty,
|
||||
R.xml.devicesettings_pairingkey,
|
||||
R.xml.devicesettings_high_mtu
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle() {
|
||||
return BONDING_STYLE_REQUIRE_KEY;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/* Copyright (C) 2017-2020 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband5;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband5.MiBand5FirmwareInfo;
|
||||
|
||||
public class MiBand5FWHelper extends HuamiFWHelper {
|
||||
|
||||
public MiBand5FWHelper(Uri uri, Context context) throws IOException {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
|
||||
firmwareInfo = new MiBand5FirmwareInfo(wholeFirmwareBytes);
|
||||
if (!firmwareInfo.isHeaderValid()) {
|
||||
throw new IllegalArgumentException("Not a Mi Band 5 firmware");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband5;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband5.MiBand5FWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
class MiBand5FWInstallHandler extends AbstractMiBandFWInstallHandler {
|
||||
MiBand5FWInstallHandler(Uri uri, Context context) {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFwUpgradeNotice() {
|
||||
return mContext.getString(R.string.fw_upgrade_notice_miband5, helper.getHumanFirmwareVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
|
||||
return new MiBand5FWHelper(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedDeviceType(GBDevice device) {
|
||||
return device.getType() == DeviceType.MIBAND5;
|
||||
}
|
||||
}
|
@ -17,18 +17,23 @@
|
||||
*/
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.itag;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
@ -45,6 +50,27 @@ public class ITagCoordinator extends AbstractDeviceCoordinator {
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle() {
|
||||
// Some iTag devices do not support bonding but some do
|
||||
return BONDING_STYLE_ASK;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public Collection<? extends ScanFilter> createBLEScanFilters() {
|
||||
ScanFilter filter = new ScanFilter.Builder()
|
||||
.setDeviceName("iTag")
|
||||
.setDeviceName("iTAG")
|
||||
.setDeviceName("ITAG")
|
||||
.setDeviceName("ITag")
|
||||
.setDeviceName("Itag")
|
||||
.setDeviceName("itag")
|
||||
.build();
|
||||
return Collections.singletonList(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.ITAG;
|
||||
|
@ -0,0 +1,31 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.lenovo;
|
||||
|
||||
public enum DataType {
|
||||
STEPS(new byte[]{0x00, 0x00}),
|
||||
SLEEP(new byte[]{0x00, 0x01}),
|
||||
HEART_RATE(new byte[]{0x00, 0x02}),
|
||||
BLOOD_PRESSURE(new byte[]{0x00, 0x06}),
|
||||
INFRARED_TEMPERATURE(new byte[]{0x00, 0x08}),
|
||||
ENVIRONMENT_TEMPERATURE(new byte[]{0x00, 0x09}),
|
||||
AIR_PRESSURE(new byte[]{0x00, 0x0A});
|
||||
|
||||
private final byte[] value;
|
||||
|
||||
DataType(byte[] value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public byte[] getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static DataType getType(int value) {
|
||||
for(DataType type : values()) {
|
||||
int intVal = (type.getValue()[1] & 0xff) | ((type.getValue()[0] & 0xff) << 8);
|
||||
if(intVal == value) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("No value defined for " + value);
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
/* Copyright (C) 2018-2019 Daniele Gobbetti, maxirnilian
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.lenovo;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.NumberPicker;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public class LenovoWatchCalibrationActivity extends AbstractGBActivity {
|
||||
|
||||
private static final String STATE_DEVICE = "stateDevice";
|
||||
private GBDevice device;
|
||||
|
||||
private NumberPicker pickerHour, pickerMinute, pickerSecond;
|
||||
|
||||
private Handler handler;
|
||||
private Runnable holdCalibration;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_watchxplus_calibration);
|
||||
|
||||
pickerHour = findViewById(R.id.np_hour);
|
||||
pickerMinute = findViewById(R.id.np_minute);
|
||||
pickerSecond = findViewById(R.id.np_second);
|
||||
|
||||
pickerHour.setMinValue(1);
|
||||
pickerHour.setMaxValue(12);
|
||||
pickerHour.setValue(12);
|
||||
pickerMinute.setMinValue(0);
|
||||
pickerMinute.setMaxValue(59);
|
||||
pickerMinute.setValue(0);
|
||||
pickerSecond.setMinValue(0);
|
||||
pickerSecond.setMaxValue(59);
|
||||
pickerSecond.setValue(0);
|
||||
|
||||
handler = new Handler();
|
||||
holdCalibration = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(LenovoWatchConstants.ACTION_CALIBRATION_HOLD));
|
||||
handler.postDelayed(this, 10000);
|
||||
}
|
||||
};
|
||||
|
||||
Intent intent = getIntent();
|
||||
device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
if (device == null && savedInstanceState != null) {
|
||||
device = savedInstanceState.getParcelable(STATE_DEVICE);
|
||||
}
|
||||
if (device == null) {
|
||||
finish();
|
||||
}
|
||||
|
||||
final Button btCalibrate = findViewById(R.id.watch9_bt_calibrate);
|
||||
btCalibrate.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
btCalibrate.setEnabled(false);
|
||||
handler.removeCallbacks(holdCalibration);
|
||||
Intent calibrationData = new Intent(LenovoWatchConstants.ACTION_CALIBRATION_SEND);
|
||||
calibrationData.putExtra(LenovoWatchConstants.VALUE_CALIBRATION_HOUR, pickerHour.getValue());
|
||||
calibrationData.putExtra(LenovoWatchConstants.VALUE_CALIBRATION_MINUTE, pickerMinute.getValue());
|
||||
calibrationData.putExtra(LenovoWatchConstants.VALUE_CALIBRATION_SECOND, pickerSecond.getValue());
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibrationData);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelable(STATE_DEVICE, device);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
device = savedInstanceState.getParcelable(STATE_DEVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
Intent calibration = new Intent(LenovoWatchConstants.ACTION_CALIBRATION);
|
||||
calibration.putExtra(LenovoWatchConstants.ACTION_ENABLE, true);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibration);
|
||||
handler.postDelayed(holdCalibration, 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
Intent calibration = new Intent(LenovoWatchConstants.ACTION_CALIBRATION);
|
||||
calibration.putExtra(LenovoWatchConstants.ACTION_ENABLE, false);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibration);
|
||||
handler.removeCallbacks(holdCalibration);
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/* Copyright (C) 2018-2019 maxirnilian
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.lenovo;
|
||||
|
||||
public class LenovoWatchConstants {
|
||||
|
||||
public static final byte RESPONSE = 0x13;
|
||||
public static final byte REQUEST = 0x31;
|
||||
|
||||
public static final byte WRITE_VALUE = 0x01;
|
||||
public static final byte READ_VALUE = 0x02;
|
||||
public static final byte TASK = 0x04;
|
||||
public static final byte KEEP_ALIVE = -0x80;
|
||||
|
||||
public static final byte[] CMD_HEADER = new byte[]{0x23, 0x01, 0x00, 0x00, 0x00};
|
||||
|
||||
// byte[] COMMAND = new byte[]{0x23, 0x01, 0x00, 0x31, 0x00, ... , 0x00}
|
||||
// | | | | | | └ Checksum
|
||||
// | | | | | └ Command + value
|
||||
// | | | | └ Sequence number
|
||||
// | | | └ Response/Request indicator
|
||||
// | | └ Value length
|
||||
// | |
|
||||
// └-----└ Header
|
||||
|
||||
public static final byte[] CMD_FIRMWARE_INFO = new byte[]{0x01, 0x02};
|
||||
public static final byte[] CMD_AUTHORIZATION_TASK = new byte[]{0x01, 0x05};
|
||||
public static final byte[] CMD_TIME_SETTINGS = new byte[]{0x01, 0x08};
|
||||
public static final byte[] CMD_ALARM_SETTINGS = new byte[]{0x01, 0x0A};
|
||||
public static final byte[] CMD_BATTERY_INFO = new byte[]{0x01, 0x14};
|
||||
|
||||
public static final byte[] CMD_NOTIFICATION_TASK = new byte[]{0x03, 0x01};
|
||||
public static final byte[] CMD_NOTIFICATION_SETTINGS = new byte[]{0x03, 0x02};
|
||||
public static final byte[] CMD_CALIBRATION_INIT_TASK = new byte[]{0x03, 0x31};
|
||||
public static final byte[] CMD_CALIBRATION_TASK = new byte[]{0x03, 0x33, 0x01};
|
||||
public static final byte[] CMD_CALIBRATION_KEEP_ALIVE = new byte[]{0x03, 0x34};
|
||||
public static final byte[] CMD_DO_NOT_DISTURB_SETTINGS = new byte[]{0x03, 0x61};
|
||||
|
||||
public static final byte[] CMD_FITNESS_GOAL_SETTINGS = new byte[]{0x10, 0x02};
|
||||
|
||||
public static final byte[] RESP_AUTHORIZATION_TASK = new byte[]{0x01, 0x01, 0x05};
|
||||
public static final byte[] RESP_BUTTON_INDICATOR = new byte[]{0x04, 0x03, 0x11};
|
||||
public static final byte[] RESP_ALARM_INDICATOR = new byte[]{-0x80, 0x01, 0x0A};
|
||||
|
||||
public static final byte[] RESP_FIRMWARE_INFO = new byte[]{0x08, 0x01, 0x02};
|
||||
public static final byte[] RESP_TIME_SETTINGS = new byte[]{0x08, 0x01, 0x08};
|
||||
public static final byte[] RESP_BATTERY_INFO = new byte[]{0x08, 0x01, 0x14};
|
||||
public static final byte[] RESP_NOTIFICATION_SETTINGS = new byte[]{0x01, 0x03, 0x02};
|
||||
|
||||
public static final String ACTION_ENABLE = "action.watch9.enable";
|
||||
|
||||
public static final String ACTION_CALIBRATION
|
||||
= "nodomain.freeyourgadget.gadgetbridge.devices.action.lenovowatch.start_calibration";
|
||||
public static final String ACTION_CALIBRATION_SEND
|
||||
= "nodomain.freeyourgadget.gadgetbridge.devices.action.lenovowatch.send_calibration";
|
||||
public static final String ACTION_CALIBRATION_HOLD
|
||||
= "nodomain.freeyourgadget.gadgetbridge.devices.action.lenovowatch.keep_calibrating";
|
||||
public static final String VALUE_CALIBRATION_HOUR
|
||||
= "value.lenovowatch.calibration_hour";
|
||||
public static final String VALUE_CALIBRATION_MINUTE
|
||||
= "value.lenovowatch.calibration_minute";
|
||||
public static final String VALUE_CALIBRATION_SECOND
|
||||
= "value.lenovowatch.calibration_second";
|
||||
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/* Copyright (C) 2018-2019 Daniele Gobbetti, maxirnilian
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.lenovo;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.DiscoveryActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class LenovoWatchPairingActivity extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LenovoWatchPairingActivity.class);
|
||||
|
||||
private static final String STATE_DEVICE_CANDIDATE = "stateDeviceCandidate";
|
||||
|
||||
private TextView message;
|
||||
private GBDeviceCandidate deviceCandidate;
|
||||
|
||||
private final BroadcastReceiver mPairingReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (GBDevice.ACTION_DEVICE_CHANGED.equals(intent.getAction())) {
|
||||
GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
LOG.debug("pairing activity: device changed: " + device);
|
||||
if (deviceCandidate.getMacAddress().equals(device.getAddress())) {
|
||||
if (device.isInitialized()) {
|
||||
pairingFinished();
|
||||
} else if (device.isConnecting() || device.isInitializing()) {
|
||||
LOG.info("still connecting/initializing device...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_watch9_pairing);
|
||||
|
||||
message = findViewById(R.id.watch9_pair_message);
|
||||
Intent intent = getIntent();
|
||||
deviceCandidate = intent.getParcelableExtra(DeviceCoordinator.EXTRA_DEVICE_CANDIDATE);
|
||||
if (deviceCandidate == null && savedInstanceState != null) {
|
||||
deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE);
|
||||
}
|
||||
if (deviceCandidate == null) {
|
||||
Toast.makeText(this, getString(R.string.message_cannot_pair_no_mac), Toast.LENGTH_SHORT).show();
|
||||
startActivity(new Intent(this, DiscoveryActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
startPairing();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelable(STATE_DEVICE_CANDIDATE, deviceCandidate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
AndroidUtils.safeUnregisterBroadcastReceiver(LocalBroadcastManager.getInstance(this), mPairingReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void startPairing() {
|
||||
message.setText(getString(R.string.pairing, deviceCandidate));
|
||||
|
||||
IntentFilter filter = new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mPairingReceiver, filter);
|
||||
|
||||
GBApplication.deviceService().disconnect();
|
||||
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
|
||||
if (device != null) {
|
||||
GBApplication.deviceService().connect(device, true);
|
||||
} else {
|
||||
GB.toast(this, "Unable to connect, can't recognize the device type: " + deviceCandidate, Toast.LENGTH_LONG, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void pairingFinished() {
|
||||
AndroidUtils.safeUnregisterBroadcastReceiver(LocalBroadcastManager.getInstance(this), mPairingReceiver);
|
||||
|
||||
Intent intent = new Intent(this, ControlCenterv2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
|
||||
finish();
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
/* Copyright (C) 2018-2019 maxirnilian
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.LenovoWatchConstants;
|
||||
|
||||
public final class WatchXPlusConstants extends LenovoWatchConstants {
|
||||
public static final UUID UUID_SERVICE_WATCHXPLUS = UUID.fromString("0000a800-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
public static final UUID UUID_UNKNOWN_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
public static final UUID UUID_CHARACTERISTIC_WRITE = UUID.fromString("0000a801-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID UUID_CHARACTERISTIC_DATABASE_READ = UUID.fromString("0000a802-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID UUID_CHARACTERISTIC_UNKNOWN_3 = UUID.fromString("0000a803-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID UUID_CHARACTERISTIC_UNKNOWN_4 = UUID.fromString("0000a804-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
|
||||
|
||||
public static final String PREF_FIND_PHONE = "prefs_find_phone";
|
||||
public static final String PREF_FIND_PHONE_DURATION = "prefs_find_phone_duration";
|
||||
|
||||
// new
|
||||
public static final String PREF_CONTINIOUS_RING = "notification_enable_continious_ring";
|
||||
public static final String PREF_REPEAT_RING = "notification_repeat_ring";
|
||||
public static final String PREF_MISSED_CALL_ENABLE = "notification_enable_missed_call";
|
||||
public static final String PREF_MISSED_CALL_REPEAT = "notification_repeat_missed_call";
|
||||
public static final String PREF_BUTTON_REJECT = "notification_button_reject";
|
||||
public static final String PREF_SHAKE_REJECT = "notification_shake_reject";
|
||||
public static final String PREF_FORCE_TIME = "pref_device_spec_settings_force_time";
|
||||
public static final String PREF_BP_CAL_LOW = "pref_sensors_bp_calibration_low";
|
||||
public static final String PREF_BP_CAL_HIGH = "pref_sensors_bp_calibration_high";
|
||||
|
||||
public static final String PREF_DO_NOT_DISTURB = "do_not_disturb_no_auto";
|
||||
public static final String PREF_DO_NOT_DISTURB_START = "do_not_disturb_no_auto_start";
|
||||
public static final String PREF_DO_NOT_DISTURB_END = "do_not_disturb_no_auto_end";
|
||||
|
||||
public static final String PREF_LONGSIT_START = "pref_longsit_start";
|
||||
public static final String PREF_LONGSIT_END = "pref_longsit_end";
|
||||
public static final String PREF_SHOW_RAW_GRAPH = "show_raw_graph";
|
||||
// moved to gear icon (per device settings)
|
||||
public static final String PREF_LANGUAGE = "language";
|
||||
|
||||
// time format constants
|
||||
public static final byte ARG_SET_TIMEMODE_24H = 0x00;
|
||||
public static final byte ARG_SET_TIMEMODE_12H = 0x01;
|
||||
|
||||
public static final int NOTIFICATION_CHANNEL_DEFAULT = 0;
|
||||
public static final int NOTIFICATION_CHANNEL_PHONE_CALL = 10;
|
||||
|
||||
public static final byte[] CMD_WEATHER_SET = new byte[]{0x01, 0x10};
|
||||
public static final byte[] CMD_RETRIEVE_DATA_COUNT = new byte[]{(byte)0xF0, 0x10};
|
||||
public static final byte[] CMD_RETRIEVE_DATA_DETAILS = new byte[]{(byte)0xF0, 0x11};
|
||||
public static final byte[] CMD_RETRIEVE_DATA_CONTENT = new byte[]{(byte)0xF0, 0x12};
|
||||
public static final byte[] CMD_REMOVE_DATA_CONTENT = new byte[]{(byte)0xF0, 0x32};
|
||||
public static final byte[] CMD_BLOOD_PRESSURE_MEASURE = new byte[]{0x05, 0x0D};
|
||||
public static final byte[] CMD_HEART_RATE_MEASURE = new byte[]{0x03, 0x23};
|
||||
public static final byte[] CMD_IS_BP_CALIBRATED = new byte[]{0x05, 0x0B};
|
||||
public static final byte[] CMD_BP_CALIBRATION = new byte[]{0x05, 0x0C};
|
||||
|
||||
public static final byte[] CMD_NOTIFICATION_TEXT_TASK = new byte[]{0x03, 0x06};
|
||||
public static final byte[] CMD_NOTIFICATION_CANCEL = new byte[]{0x03, 0x04};
|
||||
public static final byte[] CMD_NOTIFICATION_SETTINGS = new byte[]{0x03, 0x02};
|
||||
public static final byte[] CMD_POWER_MODE = new byte[]{0x03, -0x7F};
|
||||
public static final byte[] CMD_SET_DND_HOURS_TIME = new byte[]{0x03, 0x62};
|
||||
public static final byte[] CMD_SET_DND_HOURS_SWITCH = new byte[]{0x03, 0x61};
|
||||
public static final byte[] CMD_SET_PERSONAL_INFO = new byte[]{0x01, 0x0E};
|
||||
public static final byte[] CMD_INACTIVITY_REMINDER_SWITCH = new byte[]{0x03, 0x51};
|
||||
public static final byte[] CMD_INACTIVITY_REMINDER_SET = new byte[]{0x03, 0x52};
|
||||
public static final byte[] CMD_SET_UNITS = new byte[]{0x03, -0x6D};
|
||||
|
||||
public static final byte[] CMD_FITNESS_GOAL_SETTINGS = new byte[]{0x10, 0x02};
|
||||
public static final byte[] CMD_DAY_STEPS_INFO = new byte[]{0x10, 0x03};
|
||||
|
||||
public static final byte[] CMD_SHAKE_SWITCH = new byte[]{0x03, -0x6E};
|
||||
public static final byte[] CMD_DISCONNECT_REMIND = new byte[]{0x00, 0x11};
|
||||
public static final byte[] CMD_TIME_LANGUAGE = new byte[]{0x03, -0x6F};
|
||||
public static final byte[] CMD_ALTITUDE = new byte[]{0x05, 0x0A};
|
||||
|
||||
public static final byte[] RESP_SHAKE_SWITCH = new byte[]{0x08, 0x03, -0x6E};
|
||||
public static final byte[] RESP_DISCONNECT_REMIND = new byte[]{0x08, 0x00, 0x11};
|
||||
public static final byte[] RESP_IS_BP_CALIBRATED = new byte[]{0x08, 0x05, 0x0B};
|
||||
public static final byte[] RESP_BUTTON_WHILE_RING = new byte[]{0x04, 0x03, 0x03};
|
||||
public static final byte[] RESP_BP_CALIBRATION = new byte[]{0x08, 0x05, 0x0C};
|
||||
public static final byte[] RESP_SET_PERSONAL_INFO = new byte[]{0x08, 0x01, 0x0E};
|
||||
public static final byte[] RESP_GOAL_AIM_STATUS = new byte[]{0x08, 0x10, 0x02};
|
||||
public static final byte[] RESP_INACTIVITY_REMINDER_SWITCH = new byte[]{0x08, 0x03, 0x51};
|
||||
public static final byte[] RESP_INACTIVITY_REMINDER_SET = new byte[]{0x08, 0x03, 0x52};
|
||||
|
||||
public static final byte[] RESP_AUTHORIZATION_TASK = new byte[]{0x01, 0x01, 0x05};
|
||||
public static final byte[] RESP_DAY_STEPS_INDICATOR = new byte[]{0x08, 0x10, 0x03};
|
||||
public static final byte[] RESP_HEARTRATE = new byte[]{(byte) 0x80, 0x15, 0x03};
|
||||
|
||||
public static final byte[] RESP_DATA_COUNT = new byte[]{0x08, (byte)0xF0, 0x10};
|
||||
public static final byte[] RESP_DATA_DETAILS = new byte[]{0x08, (byte)0xF0, 0x11};
|
||||
public static final byte[] RESP_DATA_CONTENT = new byte[]{0x08, (byte)0xF0, 0x12};
|
||||
public static final byte[] RESP_DATA_CONTENT_REMOVE = new byte[]{-0x80, (byte)0xF0, 0x32};
|
||||
public static final byte[] RESP_BP_MEASURE_STARTED = new byte[]{0x08, 0x05, 0x0D};
|
||||
|
||||
}
|
@ -0,0 +1,283 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelUuid;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.LenovoWatchCalibrationActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.LenovoWatchPairingActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.watchxplus.WatchXPlusDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext;
|
||||
|
||||
|
||||
public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WatchXPlusDeviceSupport.class);
|
||||
private static final int FindPhone_ON = -1;
|
||||
public static final int FindPhone_OFF = 0;
|
||||
public static boolean isBPCalibrated = false;
|
||||
|
||||
private static final Prefs prefs = GBApplication.getPrefs();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public Collection<? extends ScanFilter> createBLEScanFilters() {
|
||||
ParcelUuid watchXpService = new ParcelUuid(WatchXPlusConstants.UUID_SERVICE_WATCHXPLUS);
|
||||
ScanFilter filter = new ScanFilter.Builder().setServiceUuid(watchXpService).build();
|
||||
return Collections.singletonList(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle() {
|
||||
return BONDING_STYLE_NONE;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
String macAddress = candidate.getMacAddress().toUpperCase();
|
||||
String deviceName = candidate.getName().toUpperCase();
|
||||
if (candidate.supportsService(WatchXPlusConstants.UUID_SERVICE_WATCHXPLUS)) {
|
||||
return DeviceType.WATCHXPLUS;
|
||||
} else if (macAddress.startsWith("DC:41:E5")) {
|
||||
return DeviceType.WATCHXPLUS;
|
||||
} else if (deviceName.equalsIgnoreCase("WATCH XPLUS")) {
|
||||
return DeviceType.WATCHXPLUS;
|
||||
// add initial support for Watch X non-plus (forces Watch X to be recognized as Watch XPlus)
|
||||
// Watch X non-plus have same MAC address as Watch 9 (starts with "1C:87:79")
|
||||
} else if (deviceName.equalsIgnoreCase("WATCH X")) {
|
||||
return DeviceType.WATCHXPLUS;
|
||||
}
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.WATCHXPLUS;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends Activity> getPairingActivity() {
|
||||
return LenovoWatchPairingActivity.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return new WatchXPlusSampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScreenshots() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlarmSlotCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSmartWakeup(GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Lenovo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppsManagement() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getAppsManagementActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() { return false; }
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFindDevice() { return false; }
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_liftwrist_display_noshed,
|
||||
R.xml.devicesettings_disconnectnotification_noshed,
|
||||
R.xml.devicesettings_donotdisturb_no_auto,
|
||||
R.xml.devicesettings_longsit,
|
||||
R.xml.devicesettings_find_phone,
|
||||
R.xml.devicesettings_timeformat,
|
||||
R.xml.devicesettings_power_mode,
|
||||
R.xml.devicesettings_watchxplus
|
||||
};
|
||||
}
|
||||
|
||||
// find phone settings
|
||||
/**
|
||||
* @return {@link #FindPhone_OFF}, {@link #FindPhone_ON}, or the duration
|
||||
*/
|
||||
public static int getFindPhone(SharedPreferences sharedPrefs) {
|
||||
String findPhone = sharedPrefs.getString(WatchXPlusConstants.PREF_FIND_PHONE, getContext().getString(R.string.p_off));
|
||||
|
||||
assert findPhone != null;
|
||||
if (findPhone.equals(getContext().getString(R.string.p_off))) {
|
||||
return FindPhone_OFF;
|
||||
} else if (findPhone.equals(getContext().getString(R.string.p_on))) {
|
||||
return FindPhone_ON;
|
||||
} else { // Duration
|
||||
String duration = sharedPrefs.getString(WatchXPlusConstants.PREF_FIND_PHONE_DURATION, "0");
|
||||
|
||||
try {
|
||||
int iDuration;
|
||||
|
||||
try {
|
||||
assert duration != null;
|
||||
iDuration = Integer.valueOf(duration);
|
||||
} catch (Exception ex) {
|
||||
iDuration = 60;
|
||||
}
|
||||
|
||||
return iDuration;
|
||||
} catch (Exception e) {
|
||||
return FindPhone_ON;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param startOut out Only hour/minute are used.
|
||||
* @param endOut out Only hour/minute are used.
|
||||
* @return True if DND hours are enabled.
|
||||
*/
|
||||
public static boolean getDNDHours(String deviceAddress, Calendar startOut, Calendar endOut) {
|
||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
|
||||
String doNotDisturb = prefs.getString(WatchXPlusConstants.PREF_DO_NOT_DISTURB, getContext().getString(R.string.p_off));
|
||||
|
||||
assert doNotDisturb != null;
|
||||
if (doNotDisturb.equals(getContext().getString(R.string.p_off))) {
|
||||
LOG.info(" DND is disabled ");
|
||||
return false;
|
||||
} else {
|
||||
|
||||
String start = prefs.getString(WatchXPlusConstants.PREF_DO_NOT_DISTURB_START, "01:00");
|
||||
String end = prefs.getString(WatchXPlusConstants.PREF_DO_NOT_DISTURB_END, "06:00");
|
||||
|
||||
DateFormat df = new SimpleDateFormat("HH:mm");
|
||||
|
||||
try {
|
||||
startOut.setTime(df.parse(start));
|
||||
endOut.setTime(df.parse(end));
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param startOut out Only hour/minute are used.
|
||||
* @param endOut out Only hour/minute are used.
|
||||
* @return True if DND hours are enabled.
|
||||
*/
|
||||
public static boolean getLongSitHours(String deviceAddress, Calendar startOut, Calendar endOut) {
|
||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
|
||||
boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_LONGSIT_SWITCH, false);
|
||||
|
||||
if (!enabled) {
|
||||
LOG.info(" Long sit reminder is disabled ");
|
||||
return false;
|
||||
} else {
|
||||
String start = prefs.getString(WatchXPlusConstants.PREF_LONGSIT_START, "06:00");
|
||||
String end = prefs.getString(WatchXPlusConstants.PREF_LONGSIT_END, "23:00");
|
||||
|
||||
DateFormat df = new SimpleDateFormat("HH:mm");
|
||||
|
||||
try {
|
||||
startOut.setTime(df.parse(start));
|
||||
endOut.setTime(df.parse(end));
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends Activity> getCalibrationActivity() {
|
||||
return LenovoWatchCalibrationActivity.class;
|
||||
}
|
||||
}
|
@ -0,0 +1,524 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
public class WatchXPlusSampleProvider extends AbstractSampleProvider<WatchXPlusActivitySample> {
|
||||
private GBDevice mDevice;
|
||||
private DaoSession mSession;
|
||||
|
||||
private final float movementDivisor = 950.0f;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WatchXPlusSampleProvider.class);
|
||||
|
||||
public WatchXPlusSampleProvider(GBDevice device, DaoSession session) {
|
||||
super(device, session);
|
||||
mSession = session;
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int normalizeType(int rawType) {
|
||||
return rawType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
LOG.info(" toRawActivityKind: " + activityKind);
|
||||
return activityKind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
float newIntensity = 0;
|
||||
if (rawIntensity <= 0) {
|
||||
//newIntensity = (rawIntensity * 0.5f) + 0.7f;
|
||||
newIntensity = rawIntensity;
|
||||
} else {
|
||||
newIntensity = rawIntensity / movementDivisor;
|
||||
}
|
||||
//LOG.info(" normalizeIntensity: " + rawIntensity + " to " + newIntensity);
|
||||
return newIntensity;
|
||||
//return rawIntensity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WatchXPlusActivitySample createActivitySample() {
|
||||
return new WatchXPlusActivitySample();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDao<WatchXPlusActivitySample, ?> getSampleDao() {
|
||||
return getSession().getWatchXPlusActivitySampleDao();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Property getRawKindSampleProperty() {
|
||||
return WatchXPlusActivitySampleDao.Properties.RawKind;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return WatchXPlusActivitySampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return WatchXPlusActivitySampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
// generate ActivityKind.TYPE_NOT_MEASURED if there are no data for more than 15 min. and less than 60 min.
|
||||
// generate ActivityKind.TYPE_NOT_WORN if there are no data for more than 60 min.
|
||||
@NonNull
|
||||
private List<WatchXPlusActivitySample> checkActivityData(List<WatchXPlusActivitySample> samples, int notMeasuredTS, int notWornTS) {
|
||||
int oldTS = 0;
|
||||
int newTS = 0;
|
||||
oldTS = samples.get(0).getTimestamp();
|
||||
for (int i = 0; i < samples.size(); i++) {
|
||||
//oldTS = resultList.get(i).getTimestamp();
|
||||
newTS = samples.get(i).getTimestamp();
|
||||
if ((newTS - oldTS) < notMeasuredTS) { //check data timestamp diff is more than 15 min
|
||||
oldTS = samples.get(i).getTimestamp();
|
||||
} else if (((newTS - oldTS) > notMeasuredTS) && ((newTS - oldTS) < notWornTS)) { //set data to ActivityKind.TYPE_NOT_MEASURED) if timestamp diff is more than 15 min
|
||||
samples.get(i-1).setRawKind(ActivityKind.TYPE_NOT_MEASURED);
|
||||
samples.get(i).setRawKind(ActivityKind.TYPE_NOT_MEASURED);
|
||||
oldTS = samples.get(i).getTimestamp();
|
||||
} else if ((newTS - oldTS) > notWornTS) { //set data to ActivityKind.TYPE_NOT_WORN if timestamp diff is more than 60 min
|
||||
samples.get(i-1).setRawKind(ActivityKind.TYPE_NOT_WORN);
|
||||
samples.get(i).setRawKind(ActivityKind.TYPE_NOT_WORN);
|
||||
oldTS = samples.get(i).getTimestamp();
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public List<WatchXPlusActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
|
||||
boolean showRawData = GBApplication.getDeviceSpecificSharedPrefs(mDevice.getAddress()).getBoolean(WatchXPlusConstants.PREF_SHOW_RAW_GRAPH, false);
|
||||
if (showRawData) {
|
||||
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL);
|
||||
}
|
||||
List<WatchXPlusActivitySample> samples = getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL);
|
||||
int numEntries = samples.size();
|
||||
if (numEntries < 3) {
|
||||
return samples;
|
||||
}
|
||||
|
||||
List<WatchXPlusActivitySample> resultList = new ArrayList<>(numEntries);
|
||||
|
||||
// how many elements to scan for sleep sate before and after sleep block
|
||||
int seekAhead = 10;
|
||||
boolean secondBlock = false;
|
||||
|
||||
// find sleep start and sleep stop index based on ActivityKind.TYPE_DEEP_SLEEP BLOCK 1
|
||||
int sleepStartIndex_1 = 0;
|
||||
int sleepStopIndex_1 = numEntries;
|
||||
int countNextSleepStart_1 = 0;
|
||||
int countNextSleepStop_1 = 0;
|
||||
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
if (samples.get(i).getRawKind() == ActivityKind.TYPE_DEEP_SLEEP) {
|
||||
// normalize RawIntensity
|
||||
samples.get(i).setRawIntensity(1000);
|
||||
// find sleep start index
|
||||
if (sleepStartIndex_1 == 0) {
|
||||
sleepStartIndex_1 = i;
|
||||
sleepStopIndex_1 = sleepStartIndex_1;
|
||||
countNextSleepStop_1 = sleepStopIndex_1;
|
||||
} else {
|
||||
if (countNextSleepStart_1 == 0) {
|
||||
countNextSleepStart_1 = i;
|
||||
// reset start index if next index is far ahead
|
||||
if ((countNextSleepStart_1 - sleepStartIndex_1) > seekAhead * 3) {
|
||||
sleepStartIndex_1 = countNextSleepStart_1;
|
||||
sleepStopIndex_1 = sleepStartIndex_1;
|
||||
countNextSleepStop_1 = sleepStopIndex_1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((i - sleepStopIndex_1) < (seekAhead * 4)) {
|
||||
sleepStopIndex_1 = i;
|
||||
}
|
||||
countNextSleepStop_1 = i;
|
||||
}
|
||||
}
|
||||
|
||||
// find sleep start and sleep stop index based on ActivityKind.TYPE_DEEP_SLEEP BLOCK 2
|
||||
int sleepStartIndex_2 = 0;
|
||||
int sleepStopIndex_2 = numEntries;
|
||||
int countNextSleepStart_2 = 0;
|
||||
int countNextSleepStop_2 = 0;
|
||||
int next_block = numEntries;
|
||||
|
||||
for (int i = sleepStopIndex_1 + 1; i < numEntries; i++) {
|
||||
if (samples.get(i).getRawKind() == ActivityKind.TYPE_DEEP_SLEEP) {
|
||||
// find sleep start index
|
||||
if (sleepStartIndex_2 == 0) {
|
||||
sleepStartIndex_2 = i;
|
||||
sleepStopIndex_2 = sleepStartIndex_2;
|
||||
countNextSleepStop_2 = sleepStopIndex_2;
|
||||
} else {
|
||||
if (countNextSleepStart_2 == 0) {
|
||||
countNextSleepStart_2 = i;
|
||||
// reset start index if next index is far ahead
|
||||
if ((countNextSleepStart_2 - sleepStartIndex_2) > seekAhead * 3) {
|
||||
sleepStartIndex_2 = countNextSleepStart_2;
|
||||
sleepStopIndex_2 = sleepStartIndex_2;
|
||||
countNextSleepStop_2 = sleepStopIndex_2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((i - sleepStopIndex_2) < (seekAhead * 4)) {
|
||||
sleepStopIndex_2 = i;
|
||||
}
|
||||
countNextSleepStop_2 = i;
|
||||
}
|
||||
}
|
||||
if (sleepStartIndex_2 != 0) {
|
||||
secondBlock = true;
|
||||
LOG.info(" Found second block ");
|
||||
}
|
||||
|
||||
LOG.info(" sleep_1 begin index:" + sleepStartIndex_1 + " next index: " + countNextSleepStart_1 + " sleep end index: " + sleepStopIndex_1 + " sleep end: " + countNextSleepStop_1);
|
||||
if (secondBlock) {
|
||||
LOG.info(" sleep_2 begin index:" + sleepStartIndex_2 + " next index: " + countNextSleepStart_2 + " sleep end index: " + sleepStopIndex_2 + " sleep end: " + countNextSleepStop_2);
|
||||
}
|
||||
|
||||
// SLEEP BLOCK 1
|
||||
// add all activity before sleep start
|
||||
if (secondBlock) {
|
||||
next_block = sleepStartIndex_2;
|
||||
}
|
||||
int newSleepStartIndex_1 = 0;
|
||||
if (sleepStartIndex_1 >= seekAhead) {
|
||||
newSleepStartIndex_1 = sleepStartIndex_1 - seekAhead;
|
||||
} else {
|
||||
newSleepStartIndex_1 = 0;
|
||||
}
|
||||
for (int i = 0; i < newSleepStartIndex_1; i++) {
|
||||
if (samples.get(i).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
|
||||
if (samples.get(i).getRawIntensity() <= 300) {
|
||||
samples.get(i).setRawIntensity(200);
|
||||
} else if ((samples.get(i).getRawIntensity() <= 1000) && (samples.get(i).getRawIntensity() > 100)) {
|
||||
samples.get(i).setRawIntensity(400);
|
||||
} if (samples.get(i).getRawIntensity() > 1000) {
|
||||
samples.get(i).setRawIntensity(600);
|
||||
}
|
||||
samples.get(i).setRawKind(1);
|
||||
resultList.add(samples.get(i));
|
||||
} else {
|
||||
if (samples.get(i).getRawKind() == ActivityKind.TYPE_ACTIVITY) {
|
||||
if (i < (newSleepStartIndex_1 - 3)) {
|
||||
if ((samples.get(i + 1).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) || (samples.get(i + 2).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) || (samples.get(i + 3).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP)) {
|
||||
samples.get(i).setRawKind(1);
|
||||
//samples.get(i).setRawIntensity(700);
|
||||
} else {
|
||||
samples.get(i).setRawIntensity(1000);
|
||||
}
|
||||
}
|
||||
//samples.get(i).setRawIntensity(1000);
|
||||
} else {
|
||||
samples.get(i).setRawIntensity(1000);
|
||||
}
|
||||
resultList.add(samples.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
// add sleep activity
|
||||
int newSleepStopIndex_1;
|
||||
|
||||
if ((sleepStopIndex_1 + seekAhead * 2) < next_block) {
|
||||
newSleepStopIndex_1 = sleepStopIndex_1 + seekAhead * 2;
|
||||
} else {
|
||||
newSleepStopIndex_1 = next_block;
|
||||
}
|
||||
|
||||
boolean replaceActivity_1 = false;
|
||||
for (int i = newSleepStartIndex_1; i < newSleepStopIndex_1; i++) {
|
||||
ActivitySample sample = samples.get(i);
|
||||
if (i < sleepStartIndex_1) {
|
||||
if (samples.get(i).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
|
||||
replaceActivity_1 = true;
|
||||
samples.get(i).setRawIntensity(600);
|
||||
resultList.add(samples.get(i));
|
||||
} else {
|
||||
if (replaceActivity_1) {
|
||||
samples.get(i).setRawKind(2);
|
||||
samples.get(i).setRawIntensity(600);
|
||||
resultList.add(samples.get(i));
|
||||
} else {
|
||||
samples.get(i).setRawIntensity(600);
|
||||
resultList.add(samples.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((samples.get(i).getRawKind() == ActivityKind.TYPE_DEEP_SLEEP) || (samples.get(i).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP)) {
|
||||
if (samples.get(i).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
|
||||
if (i > 0) {
|
||||
if (samples.get(i - 1).getHeartRate() > 0) {
|
||||
samples.get(i).setHeartRate(samples.get(i - 1).getHeartRate());
|
||||
}
|
||||
} else {
|
||||
if (samples.get(i + 1).getHeartRate() > 0) {
|
||||
samples.get(i).setHeartRate(samples.get(i + 1).getHeartRate());
|
||||
}
|
||||
}
|
||||
samples.get(i).setRawIntensity(600);
|
||||
resultList.add(samples.get(i));
|
||||
} else {
|
||||
samples.get(i).setRawIntensity(1000);
|
||||
resultList.add(samples.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
if ((samples.get(i).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) && (i > sleepStopIndex_1)) {
|
||||
samples.get(i).setRawIntensity(600);
|
||||
resultList.add(samples.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// add remaining activity
|
||||
if (newSleepStopIndex_1 < next_block) {
|
||||
for (int i = newSleepStopIndex_1; i < (next_block-1); i++) {
|
||||
if (samples.get(i).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
|
||||
if (samples.get(i).getRawIntensity() <= 300) {
|
||||
samples.get(i).setRawIntensity(200);
|
||||
} else if ((samples.get(i).getRawIntensity() <= 1000) && (samples.get(i).getRawIntensity() > 100)) {
|
||||
samples.get(i).setRawIntensity(400);
|
||||
} if (samples.get(i).getRawIntensity() > 1000) {
|
||||
samples.get(i).setRawIntensity(600);
|
||||
}
|
||||
samples.get(i).setRawKind(1);
|
||||
resultList.add(samples.get(i));
|
||||
} else {
|
||||
if (samples.get(i).getRawKind() == ActivityKind.TYPE_ACTIVITY) {
|
||||
if (i < (next_block - 3)) {
|
||||
if ((samples.get(i + 1).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) || (samples.get(i + 2).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) || (samples.get(i + 3).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP)) {
|
||||
samples.get(i).setRawKind(1);
|
||||
//samples.get(i).setRawIntensity(700);
|
||||
} else {
|
||||
samples.get(i).setRawIntensity(1000);
|
||||
}
|
||||
}
|
||||
//samples.get(i).setRawIntensity(1000);
|
||||
} else {
|
||||
samples.get(i).setRawIntensity(1000);
|
||||
}
|
||||
resultList.add(samples.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
// SLEEP BLOCK 2
|
||||
if (secondBlock) {
|
||||
// add sleep activity
|
||||
int newSleepStopIndex_2;
|
||||
int newSleepStartIndex_2 = 0;
|
||||
boolean replaceActivity_2 = false;
|
||||
if (sleepStartIndex_2 >= next_block + seekAhead) {
|
||||
newSleepStartIndex_2 = sleepStartIndex_2 - seekAhead;
|
||||
} else {
|
||||
newSleepStartIndex_2 = next_block;
|
||||
}
|
||||
if ((sleepStopIndex_2 + seekAhead * 2) < numEntries) {
|
||||
newSleepStopIndex_2 = sleepStopIndex_2 + seekAhead * 2;
|
||||
} else {
|
||||
newSleepStopIndex_2 = numEntries;
|
||||
}
|
||||
for (int i = newSleepStartIndex_2; i < newSleepStopIndex_2; i++) {
|
||||
ActivitySample sample = samples.get(i);
|
||||
if (i < sleepStartIndex_2) {
|
||||
if (samples.get(i).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
|
||||
replaceActivity_2 = true;
|
||||
samples.get(i).setRawIntensity(600);
|
||||
resultList.add(samples.get(i));
|
||||
} else {
|
||||
if (replaceActivity_2) {
|
||||
samples.get(i).setRawKind(2);
|
||||
samples.get(i).setRawIntensity(600);
|
||||
resultList.add(samples.get(i));
|
||||
} else {
|
||||
samples.get(i).setRawIntensity(600);
|
||||
resultList.add(samples.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((samples.get(i).getRawKind() == ActivityKind.TYPE_DEEP_SLEEP) || (samples.get(i).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP)) {
|
||||
if (samples.get(i).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
|
||||
if (i > 0) {
|
||||
if (samples.get(i - 1).getHeartRate() > 0) {
|
||||
samples.get(i).setHeartRate(samples.get(i - 1).getHeartRate());
|
||||
}
|
||||
} else {
|
||||
if (samples.get(i + 1).getHeartRate() > 0) {
|
||||
samples.get(i).setHeartRate(samples.get(i + 1).getHeartRate());
|
||||
}
|
||||
}
|
||||
samples.get(i).setRawIntensity(600);
|
||||
resultList.add(samples.get(i));
|
||||
} else {
|
||||
samples.get(i).setRawIntensity(1000);
|
||||
resultList.add(samples.get(i));
|
||||
}
|
||||
}
|
||||
if ((samples.get(i).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) && (i > sleepStopIndex_2)) {
|
||||
samples.get(i).setRawIntensity(600);
|
||||
resultList.add(samples.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
// add remaining activity
|
||||
if (newSleepStopIndex_2 < numEntries) {
|
||||
for (int i = newSleepStopIndex_2; i < (numEntries - 1); i++) {
|
||||
if (samples.get(i).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
|
||||
if (samples.get(i).getRawIntensity() <= 300) {
|
||||
samples.get(i).setRawIntensity(200);
|
||||
} else if ((samples.get(i).getRawIntensity() <= 1000) && (samples.get(i).getRawIntensity() > 100)) {
|
||||
samples.get(i).setRawIntensity(400);
|
||||
}
|
||||
if (samples.get(i).getRawIntensity() > 1000) {
|
||||
samples.get(i).setRawIntensity(600);
|
||||
}
|
||||
samples.get(i).setRawKind(1);
|
||||
resultList.add(samples.get(i));
|
||||
} else {
|
||||
if (samples.get(i).getRawKind() == ActivityKind.TYPE_ACTIVITY) {
|
||||
if (i < (numEntries - 3)) {
|
||||
if ((samples.get(i + 1).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) || (samples.get(i + 2).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP) || (samples.get(i + 3).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP)) {
|
||||
samples.get(i).setRawKind(1);
|
||||
//samples.get(i).setRawIntensity(700);
|
||||
} else {
|
||||
samples.get(i).setRawIntensity(1000);
|
||||
}
|
||||
}
|
||||
//samples.get(i).setRawIntensity(1000);
|
||||
} else {
|
||||
samples.get(i).setRawIntensity(1000);
|
||||
}
|
||||
resultList.add(samples.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// add one ActivityKind.TYPE_NOT_MEASURED at end of data
|
||||
samples.get(numEntries-1).setRawIntensity(0);
|
||||
samples.get(numEntries-1).setRawKind(ActivityKind.TYPE_NOT_MEASURED);
|
||||
samples.get(numEntries-1).setHeartRate(0);
|
||||
resultList.add(samples.get(numEntries-1));
|
||||
|
||||
// find all steps, total activity intensity and maxHR
|
||||
int totalSteps = 0;
|
||||
int maxHeartRate = 10;
|
||||
numEntries = resultList.size();
|
||||
for (int i = 0; i < numEntries-1; i++) {
|
||||
if (resultList.get(i).getRawKind() == ActivityKind.TYPE_ACTIVITY) {
|
||||
if (resultList.get(i).getSteps() > 0) {
|
||||
totalSteps = totalSteps + resultList.get(i).getSteps();
|
||||
}
|
||||
}
|
||||
if (resultList.get(i).getHeartRate() > maxHeartRate) {
|
||||
maxHeartRate = resultList.get(i).getHeartRate();
|
||||
}
|
||||
}
|
||||
|
||||
// reformat activity data based on heart rate
|
||||
int newIntensity, correctedSteps;
|
||||
int totalIntensity = 0;
|
||||
for (int i = 0; i < numEntries-1; i++) {
|
||||
if ((resultList.get(i).getRawKind() == ActivityKind.TYPE_ACTIVITY) || (resultList.get(i).getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP)) {
|
||||
if (resultList.get(i).getRawIntensity() <= 600) { // set interpolated intensity based on heart rate for every TYPE_ACTIVITY which are converted from TYPE_LIGHT_SLEEP
|
||||
if (resultList.get(i).getHeartRate() < 10) {
|
||||
newIntensity = resultList.get(i).getRawIntensity() + ((maxHeartRate - resultList.get(i+1).getHeartRate()) * 2);
|
||||
} else {
|
||||
newIntensity = resultList.get(i).getRawIntensity() + ((maxHeartRate - resultList.get(i).getHeartRate()) * 2);
|
||||
}
|
||||
} else { // because there are not RAW intensity values for every TYPE_ACTIVITY set interpolated intensity based on heart rate
|
||||
newIntensity = resultList.get(i).getRawIntensity() - ((maxHeartRate - resultList.get(i).getHeartRate()) * 2);
|
||||
}
|
||||
/*
|
||||
if (stepsPerActivity > 0.0f) { // because there are not steps values for every TYPE_ACTIVITY set interpolated steps
|
||||
correctedSteps = (int) (resultList.get(i).getRawIntensity() / stepsPerActivity);
|
||||
resultList.get(i).setSteps(correctedSteps);
|
||||
}
|
||||
*/
|
||||
resultList.get(i).setRawIntensity(newIntensity);
|
||||
if (resultList.get(i).getRawIntensity() > 0) {
|
||||
totalIntensity = totalIntensity + newIntensity;
|
||||
}
|
||||
} else { // because there are not TYPE_DEEP_SLEEP intensity set random DEEP_SLEEP intensity
|
||||
Random r = new Random();
|
||||
newIntensity = resultList.get(i).getRawIntensity() - ((maxHeartRate - (int)(r.nextFloat() * maxHeartRate)) * 2);
|
||||
resultList.get(i).setRawIntensity(newIntensity);
|
||||
if (resultList.get(i).getRawIntensity() > 0) {
|
||||
totalIntensity = totalIntensity + newIntensity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// because there are not steps values for every TYPE_ACTIVITY set interpolated steps
|
||||
float stepsPerActivity = 0.000f;
|
||||
int newTotalSteps = 0;
|
||||
int activityCount = 0;
|
||||
if (totalSteps > 0) {
|
||||
stepsPerActivity = totalIntensity / totalSteps;
|
||||
for (int i = 0; i < numEntries - 1; i++) {
|
||||
if (resultList.get(i).getRawKind() == ActivityKind.TYPE_ACTIVITY) {
|
||||
if (stepsPerActivity > 0.0f) {
|
||||
correctedSteps = (int) (resultList.get(i).getRawIntensity() / stepsPerActivity);
|
||||
resultList.get(i).setSteps(correctedSteps);
|
||||
newTotalSteps = newTotalSteps + correctedSteps;
|
||||
activityCount = activityCount + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newTotalSteps < totalSteps) {
|
||||
int stepsDiff = newTotalSteps - totalSteps;
|
||||
int increaseStepsWith = stepsDiff / activityCount;
|
||||
if (increaseStepsWith <= 1) {
|
||||
increaseStepsWith = 2;
|
||||
}
|
||||
newTotalSteps = 0;
|
||||
for (int i = 0; i < numEntries - 1; i++) {
|
||||
if (resultList.get(i).getRawKind() == ActivityKind.TYPE_ACTIVITY) {
|
||||
correctedSteps = resultList.get(i).getSteps() + increaseStepsWith;
|
||||
newTotalSteps = newTotalSteps + correctedSteps;
|
||||
if (newTotalSteps <= totalSteps) {
|
||||
resultList.get(i).setSteps(correctedSteps);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return checkActivityData(resultList, 900, 3600);
|
||||
}
|
||||
}
|
@ -20,17 +20,20 @@ package nodomain.freeyourgadget.gadgetbridge.devices.miband;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
|
||||
|
||||
@ -73,10 +76,12 @@ public abstract class AbstractMiBandFWHelper {
|
||||
public abstract int getFirmware2Version();
|
||||
|
||||
public static String formatFirmwareVersion(int version) {
|
||||
if (version == -1)
|
||||
if (version == -1) {
|
||||
return GBApplication.getContext().getString(R.string._unknown_);
|
||||
}
|
||||
|
||||
return String.format("%d.%d.%d.%d",
|
||||
return String.format(Locale.UK,
|
||||
"%d.%d.%d.%d",
|
||||
version >> 24 & 255,
|
||||
version >> 16 & 255,
|
||||
version >> 8 & 255,
|
||||
@ -122,4 +127,6 @@ public abstract class AbstractMiBandFWHelper {
|
||||
protected abstract void determineFirmwareInfo(byte[] wholeFirmwareBytes);
|
||||
|
||||
public abstract void checkValid() throws IllegalArgumentException;
|
||||
|
||||
public abstract HuamiFirmwareType getFirmwareType();
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType.WATCHFACE;
|
||||
|
||||
public abstract class AbstractMiBandFWInstallHandler implements InstallHandler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractMiBandFWInstallHandler.class);
|
||||
|
||||
@ -58,7 +60,7 @@ public abstract class AbstractMiBandFWInstallHandler implements InstallHandler {
|
||||
|
||||
protected abstract AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException;
|
||||
|
||||
protected GenericItem createInstallItem(GBDevice device) {
|
||||
private GenericItem createInstallItem(GBDevice device) {
|
||||
return new GenericItem(mContext.getString(R.string.installhandler_firmware_name, mContext.getString(device.getType().getName()), helper.getFirmwareKind(), helper.getHumanFirmwareVersion()));
|
||||
}
|
||||
|
||||
@ -98,23 +100,25 @@ public abstract class AbstractMiBandFWInstallHandler implements InstallHandler {
|
||||
return;
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (helper.isSingleFirmware()) {
|
||||
getFwUpgradeNotice();
|
||||
builder.append(getFwUpgradeNotice());
|
||||
} else {
|
||||
builder.append(mContext.getString(R.string.fw_multi_upgrade_notice, helper.getHumanFirmwareVersion(), helper.getHumanFirmwareVersion2()));
|
||||
}
|
||||
if (helper.getFirmwareType() != WATCHFACE) {
|
||||
if (helper.isSingleFirmware()) {
|
||||
getFwUpgradeNotice();
|
||||
builder.append(getFwUpgradeNotice());
|
||||
} else {
|
||||
builder.append(mContext.getString(R.string.fw_multi_upgrade_notice, helper.getHumanFirmwareVersion(), helper.getHumanFirmwareVersion2()));
|
||||
}
|
||||
|
||||
|
||||
if (helper.isFirmwareWhitelisted()) {
|
||||
builder.append(" ").append(mContext.getString(R.string.miband_firmware_known));
|
||||
fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_compatible_version));
|
||||
// TODO: set a CHECK (OKAY) button
|
||||
} else {
|
||||
builder.append(" ").append(mContext.getString(R.string.miband_firmware_unknown_warning)).append(" \n\n")
|
||||
.append(mContext.getString(R.string.miband_firmware_suggest_whitelist, String.valueOf(helper.getFirmwareVersion())));
|
||||
fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_untested_version));
|
||||
// TODO: set a UNKNOWN (question mark) button
|
||||
if (helper.isFirmwareWhitelisted()) {
|
||||
builder.append(" ").append(mContext.getString(R.string.miband_firmware_known));
|
||||
fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_compatible_version));
|
||||
// TODO: set a CHECK (OKAY) button
|
||||
} else {
|
||||
builder.append(" ").append(mContext.getString(R.string.miband_firmware_unknown_warning)).append(" \n\n")
|
||||
.append(mContext.getString(R.string.miband_firmware_suggest_whitelist, String.valueOf(helper.getFirmwareVersion())));
|
||||
fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_untested_version));
|
||||
// TODO: set a UNKNOWN (question mark) button
|
||||
}
|
||||
}
|
||||
installActivity.setInfoText(builder.toString());
|
||||
installActivity.setInstallItem(fwItem);
|
||||
|
@ -29,6 +29,7 @@ import androidx.annotation.NonNull;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.AbstractMiFirmwareInfo;
|
||||
|
||||
/**
|
||||
@ -122,6 +123,11 @@ public class MiBandFWHelper extends AbstractMiBandFWHelper {
|
||||
firmwareInfo.checkValid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HuamiFirmwareType getFirmwareType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param wholeFirmwareBytes
|
||||
* @return
|
||||
|
@ -27,12 +27,13 @@ import android.os.Bundle;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import de.greenrobot.dao.query.Query;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -219,7 +220,7 @@ public class PebblePairingActivity extends AbstractGBActivity {
|
||||
|
||||
private void performConnect(GBDevice gbDevice) {
|
||||
if (gbDevice == null) {
|
||||
gbDevice = new GBDevice(mBtDevice.getAddress(), mBtDevice.getName(), DeviceType.PEBBLE);
|
||||
gbDevice = new GBDevice(mBtDevice.getAddress(), mBtDevice.getName(), null, DeviceType.PEBBLE);
|
||||
}
|
||||
GBApplication.deviceService().connect(gbDevice);
|
||||
}
|
||||
@ -254,7 +255,7 @@ public class PebblePairingActivity extends AbstractGBActivity {
|
||||
gbDevice = deviceHelper.toGBDevice(devices.get(0));
|
||||
gbDevice.setVolatileAddress(btDevice.getAddress());
|
||||
} catch (Exception e) {
|
||||
GB.toast("Error retrieving devices from database", Toast.LENGTH_SHORT, GB.ERROR);
|
||||
GB.toast(getString(R.string.error_retrieving_devices_database), Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||
returnToPairingActivity();
|
||||
return null;
|
||||
}
|
||||
|
@ -0,0 +1,135 @@
|
||||
/* Copyright (C) 2020 Andreas Shimokawa
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.pinetime;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class PineTimeJFCoordinator extends AbstractDeviceCoordinator {
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
String name = candidate.getDevice().getName();
|
||||
if (name != null && name.startsWith("Pinetime-JF")) {
|
||||
return DeviceType.PINETIME_JF;
|
||||
}
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.PINETIME_JF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getPairingActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScreenshots() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlarmSlotCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSmartWakeup(GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Pine64";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppsManagement() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getAppsManagementActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFindDevice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
|
||||
// nothing to delete, yet
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/* Copyright (C) 2020 Andreas Shimokawa
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
|
||||
|
||||
public class FossilHRInstallHandler implements InstallHandler {
|
||||
private final Context mContext;
|
||||
private boolean mIsValid;
|
||||
private String mVersion = "(Unknown version)";
|
||||
|
||||
FossilHRInstallHandler(Uri uri, Context context) {
|
||||
mContext = context;
|
||||
UriHelper uriHelper;
|
||||
try {
|
||||
uriHelper = UriHelper.get(uri, mContext);
|
||||
} catch (IOException e) {
|
||||
mIsValid = false;
|
||||
return;
|
||||
}
|
||||
try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
|
||||
byte[] bytes = new byte[32];
|
||||
int read = in.read(bytes);
|
||||
if (read < 32) {
|
||||
mIsValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ByteBuffer buf = ByteBuffer.wrap(bytes);
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
int header0 = buf.getInt();
|
||||
buf.getInt(); // size
|
||||
int header2 = buf.getInt();
|
||||
int header3 = buf.getInt();
|
||||
if (header0 != 1 || header2 != 0x00012000 || header3 != 0x00012000) {
|
||||
mIsValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
buf.getInt(); // unknown
|
||||
int version1 = buf.get() % 0xff;
|
||||
int version2 = buf.get() & 0xff;
|
||||
mVersion = "DN1.0." + version1 + "." + version2;
|
||||
} catch (Exception e) {
|
||||
mIsValid = false;
|
||||
return;
|
||||
}
|
||||
mIsValid = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateInstallation(InstallActivity installActivity, GBDevice device) {
|
||||
if (device.isBusy()) {
|
||||
installActivity.setInfoText(device.getBusyTask());
|
||||
installActivity.setInstallEnabled(false);
|
||||
return;
|
||||
}
|
||||
if (device.getType() != DeviceType.FOSSILQHYBRID || !device.isConnected()) {
|
||||
installActivity.setInfoText("Element cannot be installed");
|
||||
installActivity.setInstallEnabled(false);
|
||||
return;
|
||||
}
|
||||
GenericItem installItem = new GenericItem();
|
||||
installItem.setIcon(R.drawable.ic_firmware);
|
||||
installItem.setName("Fossil Hybrid HR Firmware");
|
||||
installItem.setDetails(mVersion);
|
||||
|
||||
installActivity.setInfoText(mContext.getString(R.string.firmware_install_warning, "(unknown)"));
|
||||
installActivity.setInstallEnabled(true);
|
||||
installActivity.setInstallItem(installItem);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onStartInstall(GBDevice device) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return mIsValid;
|
||||
}
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -26,6 +30,7 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@ -53,6 +58,9 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
|
||||
SparseArray<String> widgetButtonsMapping = new SparseArray<>(4);
|
||||
|
||||
static public final String CONFIG_KEY_Q_ACTIONS = "Q_ACTIONS";
|
||||
private static final int REQUEST_CODE_WIDGET_EDIT = 0;
|
||||
private static final int REQUEST_CODE_IMAGE_PICK = 1;
|
||||
private static final int REQUEST_CODE_IMAGE_EDIT = 2;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -83,8 +91,9 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
|
||||
Intent startIntent = new Intent(HRConfigActivity.this, WidgetSettingsActivity.class);
|
||||
startIntent.putExtra("EXTRA_WIDGET", widget);
|
||||
startIntent.putExtra("EXTRA_WIDGET_IDNEX", position);
|
||||
startIntent.putExtra("EXTRA_WIDGET_INITIAL_NAME", ((CustomWidget) widget).getName());
|
||||
|
||||
startActivityForResult(startIntent, 0);
|
||||
startActivityForResult(startIntent, REQUEST_CODE_WIDGET_EDIT);
|
||||
}
|
||||
});
|
||||
loadCustomWidgetList();
|
||||
@ -94,7 +103,36 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
|
||||
public void onClick(View v) {
|
||||
Intent startIntent = new Intent(HRConfigActivity.this, WidgetSettingsActivity.class);
|
||||
|
||||
startActivityForResult(startIntent, 0);
|
||||
startActivityForResult(startIntent, REQUEST_CODE_WIDGET_EDIT);
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.qhybrid_set_background).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
new AlertDialog.Builder(HRConfigActivity.this)
|
||||
.setTitle("whoop whoop")
|
||||
.setMessage("background has to be pushed every time a custom widget changes, causing traffic and battery drain. Consider that when using custom widgets.")
|
||||
.setPositiveButton("ok", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Intent pickIntent = new Intent(Intent.ACTION_PICK);
|
||||
pickIntent.setType("image/*");
|
||||
|
||||
startActivityForResult(pickIntent, REQUEST_CODE_IMAGE_PICK);
|
||||
}
|
||||
})
|
||||
.setNegativeButton("nah", null)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.qhybrid_unset_background).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_SET_BACKGROUND_IMAGE);
|
||||
intent.putIntegerArrayListExtra("EXTRA_PIXELS", null);
|
||||
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(intent);
|
||||
}
|
||||
});
|
||||
|
||||
@ -138,34 +176,58 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if(data == null) return;
|
||||
if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_CREATED) {
|
||||
CustomWidget widget = (CustomWidget) data.getExtras().get("EXTRA_WIDGET");
|
||||
this.customWidgets.add(widget);
|
||||
refreshWidgetList();
|
||||
saveCustomWidgetList();
|
||||
if(requestCode == REQUEST_CODE_WIDGET_EDIT) {
|
||||
if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_CREATED) {
|
||||
CustomWidget widget = (CustomWidget) data.getExtras().get("EXTRA_WIDGET");
|
||||
this.customWidgets.add(widget);
|
||||
refreshWidgetList();
|
||||
saveCustomWidgetList();
|
||||
|
||||
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
|
||||
} else if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_UPDATED) {
|
||||
CustomWidget widget = (CustomWidget) data.getExtras().get("EXTRA_WIDGET");
|
||||
int updateIndex = data.getIntExtra("EXTRA_WIDGET_IDNEX", -1);
|
||||
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
|
||||
} else if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_UPDATED) {
|
||||
CustomWidget widget = (CustomWidget) data.getExtras().get("EXTRA_WIDGET");
|
||||
int updateIndex = data.getIntExtra("EXTRA_WIDGET_IDNEX", -1);
|
||||
|
||||
this.customWidgets.set(updateIndex, widget);
|
||||
String initialName = data.getStringExtra("EXTRA_WIDGET_INITIAL_NAME");
|
||||
String newName = widget.getName();
|
||||
|
||||
refreshWidgetList();
|
||||
saveCustomWidgetList();
|
||||
String widgetJSON = sharedPreferences.getString("FOSSIL_HR_WIDGETS", "{}");
|
||||
widgetJSON = widgetJSON.replace("custom_" + initialName, "custom_" + newName);
|
||||
sharedPreferences.edit().putString("FOSSIL_HR_WIDGETS", widgetJSON).apply();
|
||||
|
||||
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
|
||||
} else if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_DELETED){
|
||||
int updateIndex = data.getIntExtra("EXTRA_WIDGET_IDNEX", -1);
|
||||
this.customWidgets.set(updateIndex, widget);
|
||||
|
||||
this.customWidgets.remove(updateIndex);
|
||||
loadWidgetConfigs();
|
||||
refreshWidgetList();
|
||||
saveCustomWidgetList();
|
||||
|
||||
refreshWidgetList();
|
||||
saveCustomWidgetList();
|
||||
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
|
||||
} else if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_DELETED) {
|
||||
int updateIndex = data.getIntExtra("EXTRA_WIDGET_IDNEX", -1);
|
||||
|
||||
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
|
||||
this.customWidgets.remove(updateIndex);
|
||||
|
||||
refreshWidgetList();
|
||||
saveCustomWidgetList();
|
||||
|
||||
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
|
||||
}
|
||||
}else if(requestCode == REQUEST_CODE_IMAGE_PICK){
|
||||
if (resultCode == RESULT_OK)
|
||||
{
|
||||
Uri imageUri = data.getData();
|
||||
Intent activityIntent = new Intent();
|
||||
activityIntent.setClass(this, ImageEditActivity.class);
|
||||
activityIntent.setData(imageUri);
|
||||
|
||||
startActivityForResult(activityIntent, REQUEST_CODE_IMAGE_EDIT);
|
||||
}
|
||||
}else if(requestCode == REQUEST_CODE_IMAGE_EDIT){
|
||||
if(resultCode == ImageEditActivity.RESULT_CODE_EDIT_SUCCESS){
|
||||
data.setAction(QHybridSupport.QHYBRID_COMMAND_SET_BACKGROUND_IMAGE);
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void saveCustomWidgetList() {
|
||||
@ -474,6 +536,7 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
|
||||
super(HRConfigActivity.this, 0, objects);
|
||||
}
|
||||
|
||||
@SuppressLint("ResourceType")
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
|
@ -0,0 +1,75 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.parser.ActivityEntry;
|
||||
|
||||
public class HybridHRActivitySampleProvider extends AbstractSampleProvider<HybridHRActivitySample> {
|
||||
public HybridHRActivitySampleProvider(GBDevice device, DaoSession session) {
|
||||
super(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDao<HybridHRActivitySample, ?> getSampleDao() {
|
||||
return getSession().getHybridHRActivitySampleDao();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Property getRawKindSampleProperty() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return HybridHRActivitySampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return HybridHRActivitySampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int normalizeType(int rawType) {
|
||||
if(rawType == -1) return 0;
|
||||
return ActivityEntry.WEARING_STATE.fromValue((byte) rawType).getActivityKind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
return rawIntensity / 63f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HybridHRActivitySample createActivitySample() {
|
||||
return new HybridHRActivitySample();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HybridHRActivitySample> getActivitySamples(int timestamp_from, int timestamp_to) {
|
||||
return super.getActivitySamples(timestamp_from, timestamp_to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HybridHRActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
|
||||
return super.getAllActivitySamples(timestamp_from, timestamp_to);
|
||||
}
|
||||
}
|
@ -0,0 +1,260 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImageFactory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class ImageEditActivity extends AbstractGBActivity implements View.OnTouchListener {
|
||||
static final public int RESULT_CODE_EDIT_SUCCESS = 0;
|
||||
|
||||
ImageView overlay;
|
||||
Canvas overlayCanvas;
|
||||
Paint overlayPaint;
|
||||
Bitmap overlayBitmap, mainBitmap;
|
||||
|
||||
float x = 0, y = 0, diameter = 0, imageDimension = 0;
|
||||
int imageWidth, imageHeight;
|
||||
|
||||
private enum MovementState {
|
||||
MOVE_UPPER_LEFT,
|
||||
MOVE_LOWER_RIGHT,
|
||||
MOVE_FRAME
|
||||
}
|
||||
private MovementState movementState;
|
||||
float movementStartX, movementStartY, movementStartFrameX, movementStartFrameY, movementStartDiameter, leftUpperDeltaX, leftUpperDeltaY;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_qhybrid_image_edit);
|
||||
|
||||
final RelativeLayout mainLayout = findViewById(R.id.qhybrid_image_edit_container);
|
||||
overlay = findViewById(R.id.qhybrid_image_edit_image_overlay);
|
||||
overlay.setOnTouchListener(this);
|
||||
|
||||
findViewById(R.id.qhybrid_image_edit_okay).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
finalizeImage();
|
||||
}
|
||||
});
|
||||
|
||||
mainLayout.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
fitImageToLayout(mainLayout);
|
||||
} catch (IOException | RuntimeException e) {
|
||||
GB.log("Error formatting image", GB.ERROR, e);
|
||||
GB.toast("Error formatting image", Toast.LENGTH_LONG, GB.ERROR);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void finalizeImage(){
|
||||
Bitmap cropped = Bitmap.createBitmap(this.mainBitmap, (int) this.x, (int) this.y, (int) this.diameter, (int) this.diameter);
|
||||
Bitmap scaled = Bitmap.createScaledBitmap(cropped, 400, 400, false);
|
||||
cropped.recycle();
|
||||
|
||||
try {
|
||||
AssetImage image = AssetImageFactory.createAssetImage(scaled, false, 0, 0, 0);
|
||||
|
||||
Intent resultIntent = new Intent();
|
||||
resultIntent.putExtra("EXTRA_PIXELS_ENCODED", image.getFileData());
|
||||
setResult(RESULT_CODE_EDIT_SUCCESS, resultIntent);
|
||||
finish();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
|
||||
scaled.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private void fitImageToLayout(RelativeLayout mainLayout) throws IOException, RuntimeException {
|
||||
float containerHeight = mainLayout.getHeight();
|
||||
float containerWidth = mainLayout.getWidth();
|
||||
float containerRelation = containerHeight / containerWidth;
|
||||
|
||||
Bitmap bitmap = this.createImageFromURI();
|
||||
float imageHeight = bitmap.getHeight();
|
||||
float imageWidth = bitmap.getWidth();
|
||||
float imageRelation = imageHeight / imageWidth;
|
||||
|
||||
float scaleRatio;
|
||||
if(imageRelation > containerRelation){
|
||||
scaleRatio = containerHeight / imageHeight;
|
||||
}else{
|
||||
scaleRatio = containerWidth / imageWidth;
|
||||
}
|
||||
|
||||
int scaledHeight = (int)(imageHeight * scaleRatio);
|
||||
int scaledWidth = (int)(imageWidth * scaleRatio);
|
||||
|
||||
this.imageHeight = scaledHeight;
|
||||
this.imageWidth = scaledWidth;
|
||||
|
||||
this.imageDimension = this.diameter = Math.min(scaledHeight, scaledWidth);
|
||||
|
||||
mainBitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, false);
|
||||
|
||||
ImageView mainImageView = findViewById(R.id.qhybrid_image_edit_image);
|
||||
mainImageView.setImageBitmap(mainBitmap);
|
||||
createOverlay(scaledWidth, scaledHeight);
|
||||
}
|
||||
|
||||
private void createOverlay(int width, int height){
|
||||
this.overlayBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
|
||||
this.overlayCanvas = new Canvas(this.overlayBitmap);
|
||||
|
||||
this.overlayPaint = new Paint();
|
||||
this.overlayPaint.setColor(Color.BLACK);
|
||||
this.overlayPaint.setStyle(Paint.Style.STROKE);
|
||||
this.overlayPaint.setStrokeWidth(imageDimension / 100);
|
||||
|
||||
renderOverlay();
|
||||
}
|
||||
|
||||
private Bitmap createImageFromURI() throws IOException, RuntimeException {
|
||||
Uri imageURI = getIntent().getData();
|
||||
if (imageURI == null) {
|
||||
throw new RuntimeException("no image attached");
|
||||
}
|
||||
|
||||
ContentResolver resolver = getContentResolver();
|
||||
Cursor c = resolver.query(imageURI, new String[]{MediaStore.Images.ImageColumns.ORIENTATION}, null, null, null);
|
||||
c.moveToFirst();
|
||||
int orientation = c.getInt(c.getColumnIndex(MediaStore.Images.ImageColumns.ORIENTATION));
|
||||
c.close();
|
||||
Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), imageURI);
|
||||
if (orientation != 0) {
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postRotate(90);
|
||||
|
||||
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private void renderOverlay() {
|
||||
overlayCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
|
||||
|
||||
overlayCanvas.drawCircle(x, y, imageDimension / 15, overlayPaint);
|
||||
overlayCanvas.drawCircle(x + diameter, y + diameter, imageDimension / 15, overlayPaint);
|
||||
|
||||
overlayCanvas.drawCircle(x + diameter / 2, y + diameter / 2, diameter / 2, overlayPaint);
|
||||
|
||||
overlay.setImageBitmap(overlayBitmap);
|
||||
}
|
||||
|
||||
private void forceImageBoundaries(){
|
||||
this.x = Math.max(this.x, 0);
|
||||
this.y = Math.max(this.y, 0);
|
||||
|
||||
this.x = Math.min(this.x, this.imageWidth - diameter);
|
||||
this.y = Math.min(this.y, this.imageHeight - diameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||
if(motionEvent.getAction() == MotionEvent.ACTION_DOWN){
|
||||
handleTouchDown(motionEvent);
|
||||
}else if(motionEvent.getAction() == MotionEvent.ACTION_MOVE){
|
||||
handleTouchMove(motionEvent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleTouchMove(MotionEvent motionEvent){
|
||||
if(movementState == MovementState.MOVE_UPPER_LEFT){
|
||||
float moveDeltaX = motionEvent.getX() - this.movementStartX;
|
||||
float moveDeltaY = motionEvent.getY() - this.movementStartY;
|
||||
|
||||
float mid = (moveDeltaX + moveDeltaY) / 2;
|
||||
|
||||
this.diameter = this.movementStartDiameter - mid;
|
||||
this.x = this.movementStartX + mid + this.leftUpperDeltaX;
|
||||
this.y = this.movementStartY + mid + this.leftUpperDeltaY;
|
||||
}else if(movementState == MovementState.MOVE_LOWER_RIGHT) {
|
||||
float moveDeltaX = motionEvent.getX() - this.movementStartX;
|
||||
float moveDeltaY = motionEvent.getY() - this.movementStartY;
|
||||
|
||||
float mid = (moveDeltaX + moveDeltaY) / 2;
|
||||
|
||||
this.diameter = this.movementStartDiameter + mid;
|
||||
}else if(movementState == MovementState.MOVE_FRAME){
|
||||
this.x = this.movementStartFrameX + (motionEvent.getX() - this.movementStartX);
|
||||
this.y = this.movementStartFrameY + (motionEvent.getY() - this.movementStartY);
|
||||
}
|
||||
|
||||
this.forceImageBoundaries();
|
||||
renderOverlay();
|
||||
}
|
||||
|
||||
private void handleTouchDown(MotionEvent motionEvent) {
|
||||
this.movementStartX = motionEvent.getX();
|
||||
this.movementStartY = motionEvent.getY();
|
||||
this.movementStartFrameX = this.x;
|
||||
this.movementStartFrameY = this.y;
|
||||
this.movementStartDiameter = this.diameter;
|
||||
|
||||
final float threshold = imageDimension / 15;
|
||||
|
||||
float upperLeftDeltaX = this.x - motionEvent.getX();
|
||||
float upperLeftDeltaY = this.y - motionEvent.getY();
|
||||
|
||||
float upperLeftDistance = (float) Math.sqrt(upperLeftDeltaX * upperLeftDeltaX + upperLeftDeltaY * upperLeftDeltaY);
|
||||
|
||||
if(upperLeftDistance < threshold){
|
||||
// Toast.makeText(this, "upper left", 0).show();
|
||||
this.leftUpperDeltaX = upperLeftDeltaX;
|
||||
this.leftUpperDeltaY = upperLeftDeltaY;
|
||||
this.movementState = MovementState.MOVE_UPPER_LEFT;
|
||||
return;
|
||||
}
|
||||
|
||||
float lowerLeftX = this.x + diameter;
|
||||
float lowerLeftY = this.y + diameter;
|
||||
|
||||
float lowerRightDeltaX = lowerLeftX - motionEvent.getX();
|
||||
float lowerRightDeltaY = lowerLeftY - motionEvent.getY();
|
||||
|
||||
float lowerRightDistance = (float) Math.sqrt(lowerRightDeltaX * lowerRightDeltaX + lowerRightDeltaY * lowerRightDeltaY);
|
||||
|
||||
if(lowerRightDistance < threshold){
|
||||
// Toast.makeText(this, "lower right", 0).show();
|
||||
this.movementState = MovementState.MOVE_LOWER_RIGHT;
|
||||
return;
|
||||
}
|
||||
|
||||
// Toast.makeText(this, "anywhere else", 0).show();
|
||||
this.movementState = MovementState.MOVE_FRAME;
|
||||
}
|
||||
}
|
@ -85,7 +85,7 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -95,11 +95,15 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return null;
|
||||
return new HybridHRActivitySampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
if (isHybridHR()) {
|
||||
FossilHRInstallHandler installHandler = new FossilHRInstallHandler(uri, context);
|
||||
return installHandler.isValid() ? installHandler : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -121,6 +125,11 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
|
||||
return this.supportsAlarmConfiguration() ? 5 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAlarmDescription(GBDevice device) {
|
||||
return isHybridHR();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSmartWakeup(GBDevice device) {
|
||||
return false;
|
||||
@ -128,7 +137,7 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return false;
|
||||
return this.isHybridHR();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -7,6 +7,8 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.RadioButton;
|
||||
|
@ -0,0 +1,62 @@
|
||||
/* Copyright (C) 2020 Erik Bloß
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.tlw64;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public final class TLW64Constants {
|
||||
|
||||
public static final UUID UUID_SERVICE_NO1 = UUID.fromString("000055ff-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID UUID_CHARACTERISTIC_CONTROL = UUID.fromString("000033f1-0000-1000-8000-00805f9b34fb");
|
||||
public static final UUID UUID_CHARACTERISTIC_NOTIFY = UUID.fromString("000033f2-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
// Command bytes
|
||||
public static final byte CMD_DISPLAY_SETTINGS = (byte) 0xa0;
|
||||
public static final byte CMD_FIRMWARE_VERSION = (byte) 0xa1;
|
||||
public static final byte CMD_BATTERY = (byte) 0xa2;
|
||||
public static final byte CMD_DATETIME = (byte) 0xa3;
|
||||
public static final byte CMD_USER_DATA = (byte) 0xa9;
|
||||
public static final byte CMD_ALARM = (byte) 0xab;
|
||||
public static final byte CMD_FACTORY_RESET = (byte) 0xad;
|
||||
public static final byte CMD_FETCH_STEPS = (byte) 0xb2;
|
||||
public static final byte CMD_FETCH_SLEEP = (byte) 0xb3;
|
||||
public static final byte CMD_NOTIFICATION = (byte) 0xc1;
|
||||
public static final byte CMD_ICON = (byte) 0xc3;
|
||||
public static final byte CMD_DEVICE_SETTINGS = (byte) 0xd3;
|
||||
|
||||
// Notifications
|
||||
public static final byte NOTIFICATION_HEADER = (byte) 0x01;
|
||||
public static final byte NOTIFICATION_CALL = (byte) 0x02; // displays "call" on screen
|
||||
public static final byte NOTIFICATION_SMS = (byte) 0x03; // displays "mms" on screen
|
||||
public static final byte NOTIFICATION_STOP = (byte) 0x04; // to stop showing incoming call
|
||||
|
||||
// Icons
|
||||
public static final byte ICON_QQ = (byte) 0x01;
|
||||
public static final byte ICON_WECHAT = (byte) 0x02;
|
||||
public static final byte ICON_MAIL = (byte) 0x04;
|
||||
|
||||
// Alarm arguments
|
||||
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_SUNDAY = (byte) 0x01;
|
||||
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_MONDAY = (byte) 0x02;
|
||||
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_TUESDAY = (byte) 0x04;
|
||||
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_WEDNESDAY = (byte) 0x08;
|
||||
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_THURSDAY = (byte) 0x10;
|
||||
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_FRIDAY = (byte) 0x20;
|
||||
public static final byte ARG_SET_ALARM_REMINDER_REPEAT_SATURDAY = (byte) 0x40;
|
||||
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, José Rebelo, Matthieu Baerts, Nephiel, vanous, Erik Bloß
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.tlw64;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class TLW64Coordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
String name = candidate.getDevice().getName();
|
||||
if (name != null && name.startsWith("Smart Bee")) {
|
||||
return DeviceType.TLW64;
|
||||
}
|
||||
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.TLW64;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends Activity> getPairingActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return new TLW64SampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScreenshots() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlarmSlotCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSmartWakeup(GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Toleda";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppsManagement() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getAppsManagementActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFindDevice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_liftwrist_display_noshed,
|
||||
R.xml.devicesettings_longsit_noshed,
|
||||
R.xml.devicesettings_timeformat
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.tlw64;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.TLW64ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.TLW64ActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public class TLW64SampleProvider extends AbstractSampleProvider<TLW64ActivitySample> {
|
||||
|
||||
private GBDevice mDevice;
|
||||
private DaoSession mSession;
|
||||
|
||||
public TLW64SampleProvider(GBDevice device, DaoSession session) {
|
||||
super(device, session);
|
||||
|
||||
mSession = session;
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDao<TLW64ActivitySample, ?> getSampleDao() {
|
||||
return getSession().getTLW64ActivitySampleDao();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Property getRawKindSampleProperty() {
|
||||
return TLW64ActivitySampleDao.Properties.RawKind;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return TLW64ActivitySampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return TLW64ActivitySampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int normalizeType(int rawType) {
|
||||
return rawType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
return activityKind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
return rawIntensity / (float) 4000.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TLW64ActivitySample createActivitySample() {
|
||||
return new TLW64ActivitySample();
|
||||
}
|
||||
}
|
@ -63,7 +63,9 @@ public class Watch9DeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
String deviceName = candidate.getName().toUpperCase();
|
||||
if (candidate.supportsService(Watch9Constants.UUID_SERVICE_WATCH9)) {
|
||||
return DeviceType.WATCH9;
|
||||
} else if (macAddress.startsWith("1C:87:79")) {
|
||||
// add support for Watch X non-plus (same MAC address)
|
||||
// add support for Watch X Plus (same MAC address)
|
||||
} else if ((macAddress.startsWith("1C:87:79")) && ((!deviceName.equalsIgnoreCase("WATCH X")) && (!deviceName.equalsIgnoreCase("WATCH XPLUS")))) {
|
||||
return DeviceType.WATCH9;
|
||||
} else if (deviceName.equals("WATCH 9")) {
|
||||
return DeviceType.WATCH9;
|
||||
@ -161,4 +163,10 @@ public class Watch9DeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
public boolean supportsFindDevice() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends Activity> getCalibrationActivity() {
|
||||
return Watch9CalibrationActivity.class;
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +80,7 @@ public class ZeTimeConstants {
|
||||
public static final byte CMD_REMINDERS = (byte) 0x97;
|
||||
public static final byte CMD_PUSH_CALENDAR_DAY = (byte) 0x99;
|
||||
public static final byte CMD_MUSIC_CONTROL = (byte) 0xD0;
|
||||
public static final byte CMD_CALL_CONTROL = (byte) 0xDC;
|
||||
public static final byte CMD_TEST_SIGNALING = (byte) 0xFA;
|
||||
// here are the action commands
|
||||
public static final byte CMD_REQUEST = (byte) 0x70;
|
||||
|
@ -124,7 +124,7 @@ public class ZeTimeCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -163,8 +163,10 @@ public class ZeTimeCoordinator extends AbstractDeviceCoordinator {
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
R.xml.devicesettings_zetime,
|
||||
R.xml.devicesettings_timeformat,
|
||||
R.xml.devicesettings_wearlocation,
|
||||
R.xml.devicesettings_sync_calendar,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.entities;
|
||||
|
||||
public abstract class AbstractHybridHRActivitySample extends AbstractActivitySample {
|
||||
abstract public int getCalories();
|
||||
abstract public byte getWear_type();
|
||||
|
||||
@Override
|
||||
public int getRawKind() {
|
||||
return getWear_type();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawIntensity() {
|
||||
return getCalories();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserId(long userId) {}
|
||||
|
||||
@Override
|
||||
public long getUserId() {
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@ public class BluetoothStateChangeReceiver extends BroadcastReceiver {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("Bluetooth turned on => connecting...");
|
||||
LOG.info("Bluetooth turned on (ACTION_STATE_CHANGED) => connecting...");
|
||||
GBApplication.deviceService().connect();
|
||||
} else if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_OFF) {
|
||||
LOG.info("Bluetooth turned off => disconnecting...");
|
||||
|
@ -101,7 +101,7 @@ public class CalendarReceiver extends BroadcastReceiver {
|
||||
DaoSession session = dbHandler.getDaoSession();
|
||||
syncCalendar(eventList, session);
|
||||
} catch (Exception e1) {
|
||||
GB.toast("Database Error while syncing Calendar", Toast.LENGTH_SHORT, GB.ERROR);
|
||||
GB.toast("Database Error while syncing Calendar", Toast.LENGTH_SHORT, GB.ERROR, e1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.MediaMetadata;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
@ -118,6 +119,10 @@ public class NotificationListener extends NotificationListenerService {
|
||||
private long activeCallPostTime;
|
||||
private int mLastCallCommand = CallSpec.CALL_UNDEFINED;
|
||||
|
||||
private Handler mHandler = new Handler();
|
||||
private Runnable mSetMusicInfoRunnable = null;
|
||||
private Runnable mSetMusicStateRunnable = null;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
@ -276,15 +281,18 @@ public class NotificationListener extends NotificationListenerService {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (GBApplication.isRunningLollipopOrLater()) {
|
||||
if (NotificationCompat.CATEGORY_CALL.equals(sbn.getNotification().category)
|
||||
&& prefs.getBoolean("notification_support_voip_calls", false)) {
|
||||
&& prefs.getBoolean("notification_support_voip_calls", false)
|
||||
&& sbn.isOngoing()) {
|
||||
handleCallNotification(sbn);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldIgnoreNotification(sbn)) {
|
||||
LOG.info("Ignoring notification");
|
||||
return;
|
||||
if (!"com.sec.android.app.clockpackage".equals(sbn.getPackageName())) { // workaround to allow phone alarm notification
|
||||
LOG.info("Ignore notification: " + sbn.getPackageName()); // need to fix
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String source = sbn.getPackageName().toLowerCase();
|
||||
@ -460,7 +468,7 @@ public class NotificationListener extends NotificationListenerService {
|
||||
private void handleCallNotification(StatusBarNotification sbn) {
|
||||
String app = sbn.getPackageName();
|
||||
LOG.debug("got call from: " + app);
|
||||
if(app.equals("com.android.dialer")) {
|
||||
if (app.equals("com.android.dialer") || app.equals("com.android.incallui")) {
|
||||
LOG.debug("Ignoring non-voip call");
|
||||
return;
|
||||
}
|
||||
@ -616,8 +624,8 @@ public class NotificationListener extends NotificationListenerService {
|
||||
* @return true if notification was handled, false otherwise
|
||||
*/
|
||||
public boolean handleMediaSessionNotification(MediaSessionCompat.Token mediaSession) {
|
||||
MusicSpec musicSpec = new MusicSpec();
|
||||
MusicStateSpec stateSpec = new MusicStateSpec();
|
||||
final MusicSpec musicSpec = new MusicSpec();
|
||||
final MusicStateSpec stateSpec = new MusicStateSpec();
|
||||
|
||||
MediaControllerCompat c;
|
||||
try {
|
||||
@ -660,8 +668,27 @@ public class NotificationListener extends NotificationListenerService {
|
||||
musicSpec.trackNr = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER);
|
||||
|
||||
// finally, tell the device about it
|
||||
GBApplication.deviceService().onSetMusicInfo(musicSpec);
|
||||
GBApplication.deviceService().onSetMusicState(stateSpec);
|
||||
if (mSetMusicInfoRunnable != null) {
|
||||
mHandler.removeCallbacks(mSetMusicInfoRunnable);
|
||||
}
|
||||
mSetMusicInfoRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSetMusicInfo(musicSpec);
|
||||
}
|
||||
};
|
||||
mHandler.postDelayed(mSetMusicInfoRunnable, 100);
|
||||
|
||||
if (mSetMusicStateRunnable != null) {
|
||||
mHandler.removeCallbacks(mSetMusicStateRunnable);
|
||||
}
|
||||
mSetMusicStateRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSetMusicState(stateSpec);
|
||||
}
|
||||
};
|
||||
mHandler.postDelayed(mSetMusicStateRunnable, 100);
|
||||
|
||||
return true;
|
||||
} catch (NullPointerException | RemoteException e) {
|
||||
@ -794,7 +821,7 @@ public class NotificationListener extends NotificationListenerService {
|
||||
if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
|
||||
PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
if (powermanager != null && powermanager.isScreenOn()) {
|
||||
// LOG.info("Not forwarding notification, screen seems to be on and settings do not allow this");
|
||||
LOG.info("Not forwarding notification, screen seems to be on and settings do not allow this");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -26,12 +26,16 @@ import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
|
||||
public class PhoneCallReceiver extends BroadcastReceiver {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PhoneCallReceiver.class);
|
||||
|
||||
private static int mLastState = TelephonyManager.CALL_STATE_IDLE;
|
||||
private static String mSavedNumber;
|
||||
@ -45,12 +49,16 @@ public class PhoneCallReceiver extends BroadcastReceiver {
|
||||
mSavedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
|
||||
} else if(intent.getAction().equals("nodomain.freeyourgadget.gadgetbridge.MUTE_CALL")) {
|
||||
// Handle the mute request only if the phone is currently ringing
|
||||
if(mLastState != TelephonyManager.CALL_STATE_RINGING)
|
||||
if (mLastState != TelephonyManager.CALL_STATE_RINGING)
|
||||
return;
|
||||
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
mLastRingerMode = audioManager.getRingerMode();
|
||||
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
|
||||
try {
|
||||
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
|
||||
} catch (SecurityException e) {
|
||||
LOG.error("SecurityException when trying to set ringer (no permission granted :/ ?), not setting it then.");
|
||||
}
|
||||
mRestoreMutedCall = true;
|
||||
} else {
|
||||
if (intent.hasExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)) {
|
||||
|
@ -0,0 +1,44 @@
|
||||
/* Copyright (C) 2020 Andreas Shimokawa
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package nodomain.freeyourgadget.gadgetbridge.externalevents;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
|
||||
public class TinyWeatherForecastGermanyReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent != null) {
|
||||
Bundle bundle = intent.getExtras();
|
||||
if (bundle != null) {
|
||||
WeatherSpec weatherSpec = bundle.getParcelable("WeatherSpec");
|
||||
if (weatherSpec != null) {
|
||||
Weather.getInstance().setWeatherSpec(weatherSpec);
|
||||
GBApplication.deviceService().onSendWeather(weatherSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,10 @@ import android.content.Intent;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -30,9 +34,6 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||
@ -66,6 +67,7 @@ public class GBDevice implements Parcelable {
|
||||
private static final String DEVINFO_ADDR = "ADDR: ";
|
||||
private static final String DEVINFO_ADDR2 = "ADDR2: ";
|
||||
private String mName;
|
||||
private String mAlias;
|
||||
private final String mAddress;
|
||||
private String mVolatileAddress;
|
||||
private final DeviceType mDeviceType;
|
||||
@ -86,20 +88,22 @@ public class GBDevice implements Parcelable {
|
||||
private int mNotificationIconDisconnected = R.drawable.ic_notification_disconnected;
|
||||
private int mNotificationIconLowBattery = R.drawable.ic_notification_low_battery;
|
||||
|
||||
public GBDevice(String address, String name, DeviceType deviceType) {
|
||||
this(address, null, name, deviceType);
|
||||
public GBDevice(String address, String name, String alias, DeviceType deviceType) {
|
||||
this(address, null, name, alias, deviceType);
|
||||
}
|
||||
|
||||
public GBDevice(String address, String address2, String name, DeviceType deviceType) {
|
||||
public GBDevice(String address, String address2, String name, String alias, DeviceType deviceType) {
|
||||
mAddress = address;
|
||||
mVolatileAddress = address2;
|
||||
mName = (name != null) ? name : mAddress;
|
||||
mAlias = alias;
|
||||
mDeviceType = deviceType;
|
||||
validate();
|
||||
}
|
||||
|
||||
private GBDevice(Parcel in) {
|
||||
mName = in.readString();
|
||||
mAlias = in.readString();
|
||||
mAddress = in.readString();
|
||||
mVolatileAddress = in.readString();
|
||||
mDeviceType = DeviceType.values()[in.readInt()];
|
||||
@ -124,6 +128,7 @@ public class GBDevice implements Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mName);
|
||||
dest.writeString(mAlias);
|
||||
dest.writeString(mAddress);
|
||||
dest.writeString(mVolatileAddress);
|
||||
dest.writeInt(mDeviceType.ordinal());
|
||||
@ -149,10 +154,22 @@ public class GBDevice implements Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return mAlias;
|
||||
}
|
||||
|
||||
public String getAliasOrName() {
|
||||
if (mAlias != null && !mAlias.equals("")) {
|
||||
return mAlias;
|
||||
}
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
if (name == null) {
|
||||
LOG.warn("Ignoring setting of GBDevice name to null for " + this);
|
||||
@ -161,6 +178,10 @@ public class GBDevice implements Parcelable {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
mAlias = alias;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return mAddress;
|
||||
}
|
||||
|
@ -19,27 +19,33 @@ package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
|
||||
public class ActivityKind {
|
||||
public static final int TYPE_NOT_MEASURED = -1;
|
||||
public static final int TYPE_UNKNOWN = 0;
|
||||
public static final int TYPE_ACTIVITY = 1;
|
||||
public static final int TYPE_LIGHT_SLEEP = 2;
|
||||
public static final int TYPE_DEEP_SLEEP = 4;
|
||||
public static final int TYPE_NOT_WORN = 8;
|
||||
public static final int TYPE_RUNNING = 16;
|
||||
public static final int TYPE_WALKING = 32;
|
||||
public static final int TYPE_SWIMMING = 64;
|
||||
public static final int TYPE_CYCLING = 128;
|
||||
public static final int TYPE_TREADMILL = 256;
|
||||
public static final int TYPE_EXERCISE = 512;
|
||||
public static final int TYPE_UNKNOWN = 0x00000000;
|
||||
public static final int TYPE_ACTIVITY = 0x00000001;
|
||||
public static final int TYPE_LIGHT_SLEEP = 0x00000002;
|
||||
public static final int TYPE_DEEP_SLEEP = 0x00000004;
|
||||
public static final int TYPE_NOT_WORN = 0x00000008;
|
||||
public static final int TYPE_RUNNING = 0x00000010;
|
||||
public static final int TYPE_WALKING = 0x00000020;
|
||||
public static final int TYPE_SWIMMING = 0x00000040;
|
||||
public static final int TYPE_CYCLING = 0x00000080;
|
||||
public static final int TYPE_TREADMILL = 0x00000100;
|
||||
public static final int TYPE_EXERCISE = 0x00000200;
|
||||
public static final int TYPE_SWIMMING_OPENWATER = 0x00000400;
|
||||
public static final int TYPE_INDOOR_CYCLING = 0x00000800;
|
||||
public static final int TYPE_ELLIPTICAL_TRAINER = 0x00001000;
|
||||
public static final int TYPE_JUMP_ROPING = 0x00002000;
|
||||
public static final int TYPE_YOGA = 0x00004000;
|
||||
|
||||
private static final int TYPES_COUNT = 12;
|
||||
private static final int TYPES_COUNT = 17;
|
||||
|
||||
public static final int TYPE_SLEEP = TYPE_LIGHT_SLEEP | TYPE_DEEP_SLEEP;
|
||||
public static final int TYPE_ALL = TYPE_ACTIVITY | TYPE_SLEEP | TYPE_NOT_WORN;
|
||||
@ -77,6 +83,21 @@ public class ActivityKind {
|
||||
if ((types & ActivityKind.TYPE_EXERCISE) != 0) {
|
||||
result[i++] = provider.toRawActivityKind(TYPE_EXERCISE);
|
||||
}
|
||||
if ((types & ActivityKind.TYPE_SWIMMING_OPENWATER) != 0) {
|
||||
result[i++] = provider.toRawActivityKind(TYPE_SWIMMING_OPENWATER);
|
||||
}
|
||||
if ((types & ActivityKind.TYPE_INDOOR_CYCLING) != 0) {
|
||||
result[i++] = provider.toRawActivityKind(TYPE_INDOOR_CYCLING);
|
||||
}
|
||||
if ((types & ActivityKind.TYPE_ELLIPTICAL_TRAINER) != 0) {
|
||||
result[i++] = provider.toRawActivityKind(TYPE_ELLIPTICAL_TRAINER);
|
||||
}
|
||||
if ((types & ActivityKind.TYPE_JUMP_ROPING) != 0) {
|
||||
result[i++] = provider.toRawActivityKind(TYPE_JUMP_ROPING);
|
||||
}
|
||||
if ((types & ActivityKind.TYPE_YOGA) != 0) {
|
||||
result[i++] = provider.toRawActivityKind(TYPE_YOGA);
|
||||
}
|
||||
return Arrays.copyOf(result, i);
|
||||
}
|
||||
|
||||
@ -104,6 +125,16 @@ public class ActivityKind {
|
||||
return context.getString(R.string.activity_type_treadmill);
|
||||
case TYPE_EXERCISE:
|
||||
return context.getString(R.string.activity_type_exercise);
|
||||
case TYPE_SWIMMING_OPENWATER:
|
||||
return context.getString(R.string.activity_type_swimming_openwater);
|
||||
case TYPE_INDOOR_CYCLING:
|
||||
return context.getString(R.string.activity_type_indoor_cycling);
|
||||
case TYPE_ELLIPTICAL_TRAINER:
|
||||
return context.getString(R.string.activity_type_elliptical_trainer);
|
||||
case TYPE_JUMP_ROPING:
|
||||
return context.getString(R.string.activity_type_jump_roping);
|
||||
case TYPE_YOGA:
|
||||
return context.getString(R.string.activity_type_yoga);
|
||||
case TYPE_UNKNOWN:
|
||||
default:
|
||||
return context.getString(R.string.activity_type_unknown);
|
||||
@ -127,10 +158,19 @@ public class ActivityKind {
|
||||
return R.drawable.ic_activity_biking;
|
||||
case TYPE_TREADMILL:
|
||||
return R.drawable.ic_activity_walking;
|
||||
case TYPE_EXERCISE: // fall through
|
||||
case TYPE_EXERCISE:
|
||||
return R.drawable.ic_activity_exercise;
|
||||
case TYPE_SWIMMING: // fall through
|
||||
case TYPE_SWIMMING:
|
||||
case TYPE_SWIMMING_OPENWATER:
|
||||
return R.drawable.ic_activity_swimming;
|
||||
case TYPE_INDOOR_CYCLING:
|
||||
return R.drawable.ic_activity_biking; // TODO: Find a better one
|
||||
case TYPE_ELLIPTICAL_TRAINER:
|
||||
return R.drawable.ic_activity_walking; // TODO: Find a better one
|
||||
case TYPE_JUMP_ROPING:
|
||||
return R.drawable.ic_activity_exercise; // TODO: Find a better one
|
||||
case TYPE_YOGA:
|
||||
return R.drawable.ic_activity_exercise; // TODO: Find a better one
|
||||
case TYPE_NOT_WORN: // fall through
|
||||
case TYPE_ACTIVITY: // fall through
|
||||
case TYPE_UNKNOWN: // fall through
|
||||
|
@ -16,6 +16,8 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@ -25,6 +27,7 @@ import java.util.Date;
|
||||
* // TODO: split into separate entities?
|
||||
*/
|
||||
public interface ActivitySummary extends Serializable {
|
||||
Long getId();
|
||||
String getName();
|
||||
Date getStartTime();
|
||||
Date getEndTime();
|
||||
@ -35,6 +38,7 @@ public interface ActivitySummary extends Serializable {
|
||||
long getDeviceId();
|
||||
|
||||
long getUserId();
|
||||
String getSummaryData();
|
||||
// long getSteps();
|
||||
// float getDistanceMeters();
|
||||
// float getAscentMeters();
|
||||
|
@ -0,0 +1,57 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public class ActivitySummaryItems {
|
||||
private final GBDevice device;
|
||||
private int activityKindFilter;
|
||||
List<BaseActivitySummary> allItems;
|
||||
ActivitySummariesAdapter itemsAdapter;
|
||||
private int current_position = 0;
|
||||
|
||||
public ActivitySummaryItems(Context context, GBDevice device, int activityKindFilter) {
|
||||
this.device = device;
|
||||
this.activityKindFilter = activityKindFilter;
|
||||
this.itemsAdapter = new ActivitySummariesAdapter(context, device, activityKindFilter);
|
||||
}
|
||||
|
||||
public BaseActivitySummary getItem(int position){
|
||||
current_position=position;
|
||||
return itemsAdapter.getItem(position);
|
||||
}
|
||||
|
||||
public int getPosition(BaseActivitySummary item){
|
||||
return itemsAdapter.getPosition(item);
|
||||
}
|
||||
|
||||
public List<BaseActivitySummary> getAllItems(){
|
||||
return itemsAdapter.getItems();
|
||||
}
|
||||
|
||||
public BaseActivitySummary getNextItem(){
|
||||
if (current_position+1 < itemsAdapter.getCount()){
|
||||
current_position+=1;
|
||||
return itemsAdapter.getItem(current_position);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public BaseActivitySummary getPrevItem(){
|
||||
if (current_position-1 >= 0){
|
||||
current_position-=1;
|
||||
return itemsAdapter.getItem(current_position);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getCurrent_position(){
|
||||
return current_position;
|
||||
}
|
||||
|
||||
}
|
@ -52,4 +52,8 @@ public interface Alarm extends Serializable {
|
||||
int getHour();
|
||||
|
||||
int getMinute();
|
||||
|
||||
String getTitle();
|
||||
|
||||
String getDescription();
|
||||
}
|
@ -104,7 +104,7 @@ public class DailyTotals {
|
||||
}
|
||||
|
||||
|
||||
private long getTotalsStepsForActivityAmounts(ActivityAmounts activityAmounts) {
|
||||
public long getTotalsStepsForActivityAmounts(ActivityAmounts activityAmounts) {
|
||||
long totalSteps = 0;
|
||||
|
||||
for (ActivityAmount amount : activityAmounts.getAmounts()) {
|
||||
@ -131,7 +131,7 @@ public class DailyTotals {
|
||||
}
|
||||
|
||||
|
||||
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
public List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
return getAllSamples(db, device, tsFrom, tsTo);
|
||||
}
|
||||
|
||||
|
@ -35,15 +35,18 @@ public enum DeviceType {
|
||||
PEBBLE(1, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled, R.string.devicetype_pebble),
|
||||
MIBAND(10, R.drawable.ic_device_miband, R.drawable.ic_device_miband_disabled, R.string.devicetype_miband),
|
||||
MIBAND2(11, R.drawable.ic_device_miband2, R.drawable.ic_device_miband2_disabled, R.string.devicetype_miband2),
|
||||
AMAZFITBIP(12, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_amazfit_bip),
|
||||
AMAZFITBIP(12, R.drawable.ic_device_amazfit_bip, R.drawable.ic_device_amazfit_bip_disabled, R.string.devicetype_amazfit_bip),
|
||||
AMAZFITCOR(13, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_amazfit_cor),
|
||||
MIBAND3(14, R.drawable.ic_device_miband2, R.drawable.ic_device_miband2_disabled, R.string.devicetype_miband3),
|
||||
AMAZFITCOR2(15, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_amazfit_cor2),
|
||||
MIBAND4(16, R.drawable.ic_device_miband2, R.drawable.ic_device_miband2_disabled, R.string.devicetype_miband4),
|
||||
AMAZFITBIP_LITE(17, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_amazfit_bip_lite),
|
||||
AMAZFITGTR(18, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_amazfit_gtr),
|
||||
AMAZFITGTS(19, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_amazfit_gts),
|
||||
AMAZFITBIPS(20, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_amazfit_bips),
|
||||
AMAZFITBIP_LITE(17, R.drawable.ic_device_amazfit_bip, R.drawable.ic_device_amazfit_bip_disabled, R.string.devicetype_amazfit_bip_lite),
|
||||
AMAZFITGTR(18, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_amazfit_gtr),
|
||||
AMAZFITGTS(19, R.drawable.ic_device_amazfit_bip, R.drawable.ic_device_amazfit_bip_disabled, R.string.devicetype_amazfit_gts),
|
||||
AMAZFITBIPS(20, R.drawable.ic_device_amazfit_bip, R.drawable.ic_device_amazfit_bip_disabled, R.string.devicetype_amazfit_bips),
|
||||
AMAZFITGTR_LITE(21, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_amazfit_gtr),
|
||||
AMAZFITTREX(22, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_amazfit_trex),
|
||||
MIBAND5(23, R.drawable.ic_device_miband2, R.drawable.ic_device_miband2_disabled, R.string.devicetype_miband5),
|
||||
LIVEVIEW(30, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_liveview),
|
||||
HPLUS(40, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_hplus),
|
||||
MAKIBESF68(41, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_makibes_f68),
|
||||
@ -56,6 +59,8 @@ public enum DeviceType {
|
||||
ZETIME(80, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_mykronoz_zetime),
|
||||
ID115(90, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled, R.string.devicetype_id115),
|
||||
WATCH9(100, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_watch9),
|
||||
WATCHX(101, R.drawable.ic_device_watchxplus, R.drawable.ic_device_watchxplus_disabled, R.string.devicetype_watchx),
|
||||
WATCHXPLUS(102, R.drawable.ic_device_watchxplus, R.drawable.ic_device_watchxplus_disabled, R.string.devicetype_watchxplus),
|
||||
ROIDMI(110, R.drawable.ic_device_roidmi, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_roidmi),
|
||||
ROIDMI3(112, R.drawable.ic_device_roidmi, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_roidmi3),
|
||||
CASIOGB6900(120, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogb6900),
|
||||
@ -64,11 +69,12 @@ public enum DeviceType {
|
||||
MAKIBESHR3(150, R.drawable.ic_device_default, R.drawable.ic_device_hplus_disabled, R.string.devicetype_makibes_hr3),
|
||||
BANGLEJS(160, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_banglejs),
|
||||
FOSSILQHYBRID(170, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_qhybrid),
|
||||
TLW64(180, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_tlw64),
|
||||
PINETIME_JF(190, R.drawable.ic_device_pinetime, R.drawable.ic_device_pinetime_disabled, R.string.devicetype_pinetime_jf),
|
||||
MIJIA_LYWSD02(200, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled, R.string.devicetype_mijia_lywsd02),
|
||||
ITAG(250, R.drawable.ic_device_itag, R.drawable.ic_device_itag_disabled, R.string.devicetype_itag),
|
||||
VIBRATISSIMO(300, R.drawable.ic_device_lovetoy, R.drawable.ic_device_lovetoy_disabled, R.string.devicetype_vibratissimo),
|
||||
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
|
||||
|
||||
private final int key;
|
||||
@DrawableRes
|
||||
private final int defaultIcon;
|
||||
|
@ -63,6 +63,7 @@ import nodomain.freeyourgadget.gadgetbridge.externalevents.PebbleReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.PhoneCallReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.SMSReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.TinyWeatherForecastGermanyReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
@ -197,6 +198,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
private CalendarReceiver mCalendarReceiver = null;
|
||||
private CMWeatherReceiver mCMWeatherReceiver = null;
|
||||
private LineageOsWeatherReceiver mLineageOsWeatherReceiver = null;
|
||||
private TinyWeatherForecastGermanyReceiver mTinyWeatherForecastGermanyReceiver = null;
|
||||
private OmniJawsObserver mOmniJawsObserver = null;
|
||||
|
||||
private final String[] mMusicActions = {
|
||||
@ -735,25 +737,35 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
filter.addAction(AlarmClockReceiver.GOOGLE_CLOCK_ALARM_DONE_ACTION);
|
||||
registerReceiver(mAlarmClockReceiver, filter);
|
||||
}
|
||||
if (mCMWeatherReceiver == null && coordinator != null && coordinator.supportsWeather()) {
|
||||
mCMWeatherReceiver = new CMWeatherReceiver();
|
||||
registerReceiver(mCMWeatherReceiver, new IntentFilter("GB_UPDATE_WEATHER"));
|
||||
}
|
||||
if (GBApplication.isRunningOreoOrLater()) {
|
||||
if (mLineageOsWeatherReceiver == null && coordinator != null && coordinator.supportsWeather()) {
|
||||
|
||||
mLineageOsWeatherReceiver = new LineageOsWeatherReceiver();
|
||||
registerReceiver(mLineageOsWeatherReceiver, new IntentFilter("GB_UPDATE_WEATHER"));
|
||||
}
|
||||
}
|
||||
if (mOmniJawsObserver == null && coordinator != null && coordinator.supportsWeather()) {
|
||||
try {
|
||||
mOmniJawsObserver = new OmniJawsObserver(new Handler());
|
||||
getContentResolver().registerContentObserver(OmniJawsObserver.WEATHER_URI, true, mOmniJawsObserver);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
//Nothing wrong, it just means we're not running on omnirom.
|
||||
// Weather receivers
|
||||
if ( coordinator != null && coordinator.supportsWeather()) {
|
||||
if (GBApplication.isRunningOreoOrLater()) {
|
||||
if (mLineageOsWeatherReceiver == null) {
|
||||
mLineageOsWeatherReceiver = new LineageOsWeatherReceiver();
|
||||
registerReceiver(mLineageOsWeatherReceiver, new IntentFilter("GB_UPDATE_WEATHER"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (mCMWeatherReceiver == null) {
|
||||
mCMWeatherReceiver = new CMWeatherReceiver();
|
||||
registerReceiver(mCMWeatherReceiver, new IntentFilter("GB_UPDATE_WEATHER"));
|
||||
}
|
||||
}
|
||||
if (mTinyWeatherForecastGermanyReceiver == null) {
|
||||
mTinyWeatherForecastGermanyReceiver = new TinyWeatherForecastGermanyReceiver();
|
||||
registerReceiver(mTinyWeatherForecastGermanyReceiver, new IntentFilter("de.kaffeemitkoffein.broadcast.WEATHERDATA"));
|
||||
}
|
||||
if (mOmniJawsObserver == null) {
|
||||
try {
|
||||
mOmniJawsObserver = new OmniJawsObserver(new Handler());
|
||||
getContentResolver().registerContentObserver(OmniJawsObserver.WEATHER_URI, true, mOmniJawsObserver);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
//Nothing wrong, it just means we're not running on omnirom.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GBApplication.getPrefs().getBoolean("auto_fetch_enabled", false) &&
|
||||
coordinator != null && coordinator.supportsActivityDataFetching() && mGBAutoFetchReceiver == null) {
|
||||
mGBAutoFetchReceiver = new GBAutoFetchReceiver();
|
||||
@ -807,6 +819,11 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
}
|
||||
if (mOmniJawsObserver != null) {
|
||||
getContentResolver().unregisterContentObserver(mOmniJawsObserver);
|
||||
mOmniJawsObserver = null;
|
||||
}
|
||||
if (mTinyWeatherForecastGermanyReceiver != null) {
|
||||
unregisterReceiver(mTinyWeatherForecastGermanyReceiver);
|
||||
mTinyWeatherForecastGermanyReceiver = null;
|
||||
}
|
||||
if (mGBAutoFetchReceiver != null) {
|
||||
unregisterReceiver(mGBAutoFetchReceiver);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user