1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-27 19:15:50 +01:00

Merge branch 'master' of codeberg.org:Freeyourgadget/Gadgetbridge

This commit is contained in:
Gordon Williams 2020-08-17 10:59:11 +01:00
commit d98f8d7484
341 changed files with 20403 additions and 1878 deletions

View File

@ -5,9 +5,9 @@ about: Create a report to help us improve
--- ---
#### Before reporting a bug, please confirm the following: #### 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 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://github.com/Freeyourgadget/Gadgetbridge/issues), 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 [github policies and terms of services](https://help.github.com/articles/github-terms-of-service/#1-responsibility-for-user-generated-content) - [ ] 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: ### I got Gadgetbridge from:
* [ ] F-Droid * [ ] 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. 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: #### 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: #### Your wearable device is:

View File

@ -5,9 +5,9 @@ about: Create a report to help us improve
--- ---
#### Before reporting a bug, please confirm the following: #### 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 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://github.com/Freeyourgadget/Gadgetbridge/issues), 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 [github policies and terms of services](https://help.github.com/articles/github-terms-of-service/#1-responsibility-for-user-generated-content) - [ ] 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: ### I got Gadgetbridge from:
* [ ] F-Droid * [ ] 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. 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: #### 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: #### Your wearable device is:

View File

@ -5,12 +5,12 @@ about: Suggest an idea for this project
--- ---
#### Before requesting a new feature, please confirm the following: #### 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 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://github.com/Freeyourgadget/Gadgetbridge/issues), 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 [github policies and terms of services](https://help.github.com/articles/github-terms-of-service/#1-responsibility-for-user-generated-content) - [ ] 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: #### 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: #### Your wearable device is:

View File

@ -19,10 +19,10 @@ android:
- tools - tools
# The BuildTools version used by your project # 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 # The SDK version used to compile your project
- android-28 - android-29
# Additional components # Additional components
- extra-android-m2repository - extra-android-m2repository

View File

@ -1,4 +1,90 @@
### Changelog ### 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 #### Version 0.43.0
* Initial support for Fossil Hybrid HR (needs complicated key extraction, read wiki) * 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 * Fossil: Allow switching off the Q Icon and use the default Gadgetbridge icon

View File

@ -1,28 +1,28 @@
## Feature Matrix ## Feature Matrix
| | Pebble OG | Pebble Time/2 | Mi Band | Mi Band 2 | Mi Band 3 | Amazfit Bip | Amazfit Cor | | | 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 | |Calls Notification | YES | YES | YES | YES | YES | YES | YES | YES |
|Reject Calls | YES | YES | NO | NO | YES | YES | YES | |Reject Calls | YES | YES | NO | NO | YES | YES | YES | YES |
|Accept Calls | NO(2) | NO(2) | NO | NO | NO | NO | NO | |Accept Calls | NO(2) | NO(2) | NO | NO | NO | NO | NO | NO |
|Generic Notification | YES | YES | YES | YES | YES | YES | YES | |Generic Notification | YES | YES | YES | YES | YES | YES | YES | YES |
|Dismiss Notifications on Phone | YES | YES | NO | NO | NO | NO | NO | |Dismiss Notifications on Phone | YES | YES | NO | NO | NO | NO | NO | NO |
|Predefined Replies | YES | YES | 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 | |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(3) | NO | |Calendar Sync | YES | YES | NO | NO | NO | NO | NO(3) | NO |
|Configure alarms from Gadgetbridge | NO | NO | YES | YES | YES | YES | YES | |Configure alarms from Gadgetbridge | NO | NO | YES | YES | YES | YES(1) | YES | YES |
|Smart alarms | NO(1) | YES | YES | NO | NO | NO | NO | |Smart alarms | NO(1) | YES | YES | NO | NO | NO | NO | NO |
|Weather | NO(1) | YES | NO | NO | YES | YES | YES | |Weather | NO(1) | YES | NO | NO | YES | YES | YES | YES |
|Activity Tracking | NO(1) | YES | YES | YES | YES | YES | YES | |Activity Tracking | NO(1) | YES | YES | YES | YES | YES | YES | YES |
|GPS tracks import | NO | NO | NO | NO | NO | YES | NO | |GPS tracks import | NO | NO | NO | NO | NO | NO | YES | NO |
|Sleep Tracking | NO(1) | YES | YES | YES | YES | YES | YES | |Sleep Tracking | NO(1) | YES | YES | YES | YES | YES | YES | YES |
|HR Tracking | N/A | 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 | |Realtime Activity Tracking | NO | NO | YES | YES | YES | YES | YES | YES |
|Music Control | YES | YES | NO | NO | NO | NO | YES | |Music Control | YES | YES | NO | NO | NO | YES | NO | YES |
|Watchapp/face Installation | YES | YES | NO | NO | NO | YES | YES | |Watchapp/face Installation | YES | YES | NO | NO | NO | YES | YES | YES |
|Firmware Installation | YES | YES | YES | YES | YES | YES | YES | |Firmware Installation | YES | YES | YES | YES | YES | YES | YES | YES |
|Taking Screenshots | YES | YES | NO | NO | NO | NO | NO | |Taking Screenshots | YES | YES | NO | NO | NO | NO | NO | NO |
|Support Android Companion Apps | YES | YES | NO | NO | NO | NO | NO | |Support Android Companion Apps | YES | YES | NO | NO | NO | NO | NO | NO |
(1) Possible via 3rd Party Watchapp (1) Possible via 3rd Party Watchapp
(2) Theoretically possible (works on iOS, would need lot of work) (2) Theoretically possible (works on iOS, would need lot of work)

View File

@ -6,9 +6,9 @@ archivesBaseName = 'gadgetbridge-daogenerator'
//version = '0.9.2-SNAPSHOT' //version = '0.9.2-SNAPSHOT'
dependencies { dependencies {
// compile 'org.greenrobot:greendao-generator:2.2.0' // implementation 'org.greenrobot:greendao-generator:2.2.0'
// compile project(":DaoGenerator") // implementation project(":DaoGenerator")
compile 'com.github.Freeyourgadget:greendao:1998d7cd2d21f662c6044f6ccf3b3a251bbad341' implementation 'com.github.Freeyourgadget:greendao:1998d7cd2d21f662c6044f6ccf3b3a251bbad341'
} }
sourceSets { sourceSets {

View File

@ -43,7 +43,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception { 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 userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes); Entity user = addUserInfo(schema, userAttributes);
@ -71,6 +71,11 @@ public class GBDaoGenerator {
addZeTimeActivitySample(schema, user, device); addZeTimeActivitySample(schema, user, device);
addID115ActivitySample(schema, user, device); addID115ActivitySample(schema, user, device);
addJYouActivitySample(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); addCalendarSyncState(schema, device);
addAlarms(schema, user, device); addAlarms(schema, user, device);
@ -78,7 +83,7 @@ public class GBDaoGenerator {
addNotificationFilterEntry(schema, notificationFilter); addNotificationFilterEntry(schema, notificationFilter);
addBipActivitySummary(schema, user, device); addActivitySummary(schema, user, device);
new DaoGenerator().generateAll(schema, "app/src/main/java"); 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.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.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("model").javaDocGetterAndSetter("An optional model, further specifying the kind of device-");
device.addStringProperty("alias");
Property deviceId = deviceAttributes.addLongProperty("deviceId").notNull().getProperty(); Property deviceId = deviceAttributes.addLongProperty("deviceId").notNull().getProperty();
// sorted by the from-date, newest first // sorted by the from-date, newest first
Property deviceAttributesSortProperty = getPropertyByName(deviceAttributes, VALID_FROM_UTC); Property deviceAttributesSortProperty = getPropertyByName(deviceAttributes, VALID_FROM_UTC);
@ -341,6 +347,63 @@ public class GBDaoGenerator {
return activitySample; 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) { private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
activitySample.setSuperclass(superClass); activitySample.setSuperclass(superClass);
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider"); activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
@ -391,6 +454,8 @@ public class GBDaoGenerator {
alarm.addIntProperty("hour").notNull(); alarm.addIntProperty("hour").notNull();
alarm.addIntProperty("minute").notNull(); alarm.addIntProperty("minute").notNull();
alarm.addBooleanProperty("unused").notNull(); alarm.addBooleanProperty("unused").notNull();
alarm.addStringProperty("title");
alarm.addStringProperty("description");
alarm.addToOne(user, userId); alarm.addToOne(user, userId);
alarm.addToOne(device, deviceId); alarm.addToOne(device, deviceId);
} }
@ -419,7 +484,7 @@ public class GBDaoGenerator {
return notificatonFilter; 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"); Entity summary = addEntity(schema, "BaseActivitySummary");
summary.implementsInterface(ACTIVITY_SUMMARY); summary.implementsInterface(ACTIVITY_SUMMARY);
summary.addIdProperty(); summary.addIdProperty();
@ -442,6 +507,8 @@ public class GBDaoGenerator {
summary.addToOne(device, deviceId); summary.addToOne(device, deviceId);
Property userId = summary.addLongProperty("userId").notNull().codeBeforeGetter(OVERRIDE).getProperty(); Property userId = summary.addLongProperty("userId").notNull().codeBeforeGetter(OVERRIDE).getProperty();
summary.addToOne(user, userId); summary.addToOne(user, userId);
summary.addStringProperty("summaryData");
summary.addByteArrayProperty("rawSummaryData");
} }
private static Property findProperty(Entity entity, String propertyName) { private static Property findProperty(Entity entity, String propertyName) {

View File

@ -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): Creative Commons Attribution 3.0 United States (CC BY-3.0 US):
ic_donate by Peter van Driel https://thenounproject.com/term/donate/239009/ 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

View File

@ -27,13 +27,15 @@ vendor's servers.
[List of changes](https://codeberg.org/Freeyourgadget/Gadgetbridge/src/master/CHANGELOG.md) [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 [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 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 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Cor)
* Amazfit Cor 2 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Cor-2) * 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 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 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 * BFH-16
* Casio GB-6900B * 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) * 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 * ID115
* JYou Y5 * JYou Y5
* Lenovo Watch 9 * Lenovo Watch 9
* Lenovo Watch X (Plus) [Wiki](https://codeberg.org/mamutcho/Gadgetbridge/wiki)
* Liveview * Liveview
* Makibes HR3 * Makibes HR3
* Mi Band, Mi Band 1A, Mi Band 1S [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band) * 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 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 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) * Mi Scale 2 (Currently only displays a toast after stepping on the scale)
* NO.1 F1 * NO.1 F1
* Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, Pebble Time Round [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Pebble) * 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) * Pebble 2 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Pebble)
* Teclast H10, H30 * Teclast H10, H30
* TLW64
* XWatch (Affordable Chinese Casio-like smartwatches) * XWatch (Affordable Chinese Casio-like smartwatches)
* Vibratissimo (Experimental) * Vibratissimo (Experimental)
* ZeTime [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/MyKronoz-ZeTime) * 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) * Sebastian Kranz (ZeTime)
* Vadim Kaushan (ID115) * Vadim Kaushan (ID115)
* "maxirnilian" (Lenovo Watch 9) * "maxirnilian" (Lenovo Watch 9)
* "ksiwczynski", "mkusnierz", "mamutcho" (Lenovo Watch X Plus)
* Andreas Böhler (Casio GB-6900B) * Andreas Böhler (Casio GB-6900B)
* Jean-François Greffier (Mi Scale 2) * Jean-François Greffier (Mi Scale 2)
* Johannes Schmitt (BFH-16) * Johannes Schmitt (BFH-16)
@ -86,6 +92,7 @@ Please see [FEATURES.md](https://codeberg.org/Freeyourgadget/Gadgetbridge/src/ma
* Gordon Williams (Bangle.js) * Gordon Williams (Bangle.js)
* Pavel Elagin (JYou Y5) * Pavel Elagin (JYou Y5)
* Taavi Eomäe (iTag) * Taavi Eomäe (iTag)
* Erik Bloß (TLW64)
## Contribute ## Contribute

View File

@ -16,17 +16,17 @@ android {
sourceCompatibility JavaVersion.VERSION_1_7 sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7
} }
compileSdkVersion 28 compileSdkVersion 29
buildToolsVersion '28.0.3' buildToolsVersion '29.0.3'
defaultConfig { defaultConfig {
applicationId "nodomain.freeyourgadget.gadgetbridge" applicationId "nodomain.freeyourgadget.gadgetbridge"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 28 targetSdkVersion 29
// Note: always bump BOTH versionCode and versionName! // Note: always bump BOTH versionCode and versionName!
versionName "0.43.0" versionName "0.46.0"
versionCode 169 versionCode 178
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
buildTypes { buildTypes {
@ -68,7 +68,7 @@ dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "androidx.appcompat:appcompat:1.1.0" 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.cardview:cardview:1.0.0"
implementation "androidx.recyclerview:recyclerview:1.1.0" implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.legacy:legacy-support-v4:1.0.0" implementation "androidx.legacy:legacy-support-v4:1.0.0"

View File

@ -31,4 +31,13 @@
-keep class **$Properties -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.** { *; }

View File

@ -1,33 +1,53 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="nodomain.freeyourgadget.gadgetbridge"> package="nodomain.freeyourgadget.gadgetbridge">
<!-- <!--
Comment in for testing Pebble Emulator Comment in for testing Pebble Emulator
<uses-permission android:name="android.permission.INTERNET" /> <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" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <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_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" /> <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.CALL_PHONE" />
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" /> <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CALENDAR" /> <uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" /> 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.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <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.ACCESS_WEATHER_MANAGER" />
<uses-permission android:name="cyanogenmod.permission.READ_WEATHER" /> <uses-permission android:name="cyanogenmod.permission.READ_WEATHER" />
<uses-permission android:name="lineageos.permission.ACCESS_WEATHER_MANAGER" /> <uses-permission android:name="lineageos.permission.ACCESS_WEATHER_MANAGER" />
<uses-permission android:name="lineageos.permission.READ_WEATHER" /> <uses-permission android:name="lineageos.permission.READ_WEATHER" />
<uses-permission android:name="org.omnirom.omnijaws.READ_WEATHER" /> <uses-permission android:name="org.omnirom.omnijaws.READ_WEATHER" />
<uses-feature <uses-feature
android:name="android.hardware.bluetooth" android:name="android.hardware.bluetooth"
android:required="true" /> android:required="true" />
@ -37,6 +57,9 @@
<uses-feature <uses-feature
android:name="android.hardware.telephony" android:name="android.hardware.telephony"
android:required="false" /> android:required="false" />
<uses-feature
android:name="android.software.companion_device_setup"
android:required="false" />
<application <application
android:name=".GBApplication" android:name=".GBApplication"
@ -46,7 +69,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/GadgetbridgeTheme"> android:theme="@style/GadgetbridgeTheme">
<activity android:name=".devices.qhybrid.WidgetSettingsActivity"></activity> <activity android:name=".devices.qhybrid.WidgetSettingsActivity" />
<activity <activity
android:name=".activities.ControlCenterv2" android:name=".activities.ControlCenterv2"
android:label="@string/title_activity_controlcenter" android:label="@string/title_activity_controlcenter"
@ -292,6 +315,7 @@
<data android:mimeType="application/octet-stream" /> <data android:mimeType="application/octet-stream" />
</intent-filter> </intent-filter>
<!-- to receive firmwares from the download content provider if recognized as zip --> <!-- to receive firmwares from the download content provider if recognized as zip -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -301,6 +325,7 @@
<data android:mimeType="application/zip" /> <data android:mimeType="application/zip" />
<data android:mimeType="application/x-zip-compressed" /> <data android:mimeType="application/x-zip-compressed" />
</intent-filter> </intent-filter>
<!-- to receive files from the "share" intent --> <!-- to receive files from the "share" intent -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
@ -345,13 +370,21 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver <receiver
android:name=".externalevents.BluetoothStateChangeReceiver" android:name=".externalevents.BluetoothStateChangeReceiver"
android:exported="false"> android:exported="true"
android:permission="android.permission.BLUETOOTH,android.permission.BLUETOOTH_ADMIN">
<intent-filter> <intent-filter>
<action android:name="android.bluetooth.adapter.action.STATE_CHANGED" /> <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> </intent-filter>
</receiver> </receiver>
<receiver <receiver
android:name=".service.receivers.GBMusicControlReceiver" android:name=".service.receivers.GBMusicControlReceiver"
android:exported="false"> android:exported="false">
@ -369,7 +402,7 @@
<receiver <receiver
android:name=".database.PeriodicExporter" android:name=".database.PeriodicExporter"
android:enabled="true" android:enabled="true"
android:exported="false"></receiver> android:exported="false" />
<!-- <!--
forcing the DebugActivity to portrait mode avoids crashes with the progress forcing the DebugActivity to portrait mode avoids crashes with the progress
dialog when changing orientation dialog when changing orientation
@ -380,6 +413,19 @@
android:parentActivityName=".activities.ControlCenterv2" android:parentActivityName=".activities.ControlCenterv2"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden" /> 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 <activity
android:name=".activities.DbManagementActivity" android:name=".activities.DbManagementActivity"
android:label="@string/title_activity_db_management" android:label="@string/title_activity_db_management"
@ -405,6 +451,12 @@
<activity <activity
android:name=".devices.watch9.Watch9CalibrationActivity" android:name=".devices.watch9.Watch9CalibrationActivity"
android:label="@string/title_activity_watch9_calibration" /> 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 <activity
android:name=".activities.charts.ChartsActivity" android:name=".activities.charts.ChartsActivity"
android:label="@string/title_activity_charts" android:label="@string/title_activity_charts"
@ -438,6 +490,7 @@
android:name=".contentprovider.PebbleContentProvider" android:name=".contentprovider.PebbleContentProvider"
android:authorities="com.getpebble.android.provider" android:authorities="com.getpebble.android.provider"
android:exported="true" /> android:exported="true" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.screenshot_provider" android:authorities="${applicationId}.screenshot_provider"
@ -498,6 +551,7 @@
<data android:scheme="gadgetbridge" /> <data android:scheme="gadgetbridge" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".devices.qhybrid.ConfigActivity" android:name=".devices.qhybrid.ConfigActivity"
android:exported="true" /> android:exported="true" />
@ -507,6 +561,8 @@
<activity <activity
android:name=".devices.qhybrid.HRConfigActivity" android:name=".devices.qhybrid.HRConfigActivity"
android:exported="true" /> android:exported="true" />
<activity
android:name=".devices.qhybrid.ImageEditActivity"
android:exported="true" />
</application> </application>
</manifest> </manifest>

View File

@ -22,6 +22,7 @@ import android.app.Application;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.NotificationManager.Policy; import android.app.NotificationManager.Policy;
import android.app.UiModeManager;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -42,6 +43,7 @@ import android.provider.ContactsContract.PhoneLookup;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.io.File; 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.AMAZFITBIP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITCOR; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITCOR;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITCOR2; 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.MIBAND;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND2; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND2;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND3; 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.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_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 * Main Application class that initializes and provides access to certain things like
* logging and DB access. * logging and DB access.
@ -107,6 +106,9 @@ public class GBApplication extends Application {
private static final String PREFS_VERSION = "shared_preferences_version"; 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 //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 CURRENT_PREFS_VERSION = 7;
private static final int ERROR_IN_GADGETBRIDGE_NOTIFICATION = 42;
private static LimitedQueue mIDSenderLookup = new LimitedQueue(16); private static LimitedQueue mIDSenderLookup = new LimitedQueue(16);
private static Prefs prefs; private static Prefs prefs;
private static GBPrefs gbPrefs; private static GBPrefs gbPrefs;
@ -211,7 +213,7 @@ public class GBApplication extends Application {
notificationManager.createNotificationChannel(channel); notificationManager.createNotificationChannel(channel);
} }
NotificationChannel channelHighPr = notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID ); NotificationChannel channelHighPr = notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID);
if (channelHighPr == null) { if (channelHighPr == null) {
channelHighPr = new NotificationChannel(NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID, channelHighPr = new NotificationChannel(NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID,
getString(R.string.notification_channel_high_priority_name), getString(R.string.notification_channel_high_priority_name),
@ -222,7 +224,23 @@ public class GBApplication extends Application {
bluetoothStateChangeReceiver = new BluetoothStateChangeReceiver(); bluetoothStateChangeReceiver = new BluetoothStateChangeReceiver();
registerReceiver(bluetoothStateChangeReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); 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: case MIBAND4:
newWearside = prefs.getString("mi_wearside", "left"); newWearside = prefs.getString("mi_wearside", "left");
break; break;
case MIBAND5:
newWearside = prefs.getString("mi_wearside", "left");
break;
case HPLUS: case HPLUS:
newWearside = prefs.getString("hplus_wrist", "left"); newWearside = prefs.getString("hplus_wrist", "left");
newTimeformat = prefs.getString("hplus_timeformat", "24h"); newTimeformat = prefs.getString("hplus_timeformat", "24h");
@ -937,7 +958,13 @@ public class GBApplication extends Application {
} }
public static boolean isDarkThemeEnabled() { 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) { public static int getTextColor(Context context) {

View File

@ -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());
}
}

View File

@ -35,6 +35,10 @@ public abstract class AbstractListActivity<T> extends AbstractGBActivity {
this.itemAdapter.loadItems(); this.itemAdapter.loadItems();
} }
public void setActivityKindFilter(int activityKind){
this.itemAdapter.setActivityKindFilter(activityKind);
}
public AbstractItemAdapter<T> getItemAdapter() { public AbstractItemAdapter<T> getItemAdapter() {
return itemAdapter; return itemAdapter;
} }

View File

@ -32,8 +32,10 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.AbsListView; import android.widget.AbsListView;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.DatePicker; import android.widget.DatePicker;
import android.widget.ListView; import android.widget.ListView;
import android.widget.Spinner;
import android.widget.Toast; import android.widget.Toast;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
@ -46,6 +48,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -54,15 +57,22 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter; import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary; import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummary; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes; import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ActivitySummariesActivity extends AbstractListActivity<BaseActivitySummary> { public class ActivitySummariesActivity extends AbstractListActivity<BaseActivitySummary> {
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummariesActivity.class);
private GBDevice mGBDevice; private GBDevice mGBDevice;
private SwipeRefreshLayout swipeLayout; private SwipeRefreshLayout swipeLayout;
LinkedHashMap<String , Integer> activityKindMap = new LinkedHashMap<>(1);
int activityFilter=0;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
@ -121,7 +131,8 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setItemAdapter(new ActivitySummariesAdapter(this, mGBDevice));
setItemAdapter(new ActivitySummariesAdapter(this, mGBDevice,activityFilter));
getItemListView().setOnItemClickListener(new AdapterView.OnItemClickListener() { getItemListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override @Override
@ -129,13 +140,12 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
Object item = parent.getItemAtPosition(position); Object item = parent.getItemAtPosition(position);
if (item != null) { if (item != null) {
ActivitySummary summary = (ActivitySummary) item; ActivitySummary summary = (ActivitySummary) item;
try {
String gpxTrack = summary.getGpxTrack(); showActivityDetail(position);
if (gpxTrack != null) { } catch (Exception e) {
showTrack(gpxTrack); GB.toast(getApplicationContext(), "Unable to display Activity Detail, maybe the activity is not available yet: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
} else {
GB.toast("This activity does not contain GPX tracks.", Toast.LENGTH_LONG, GB.INFO);
} }
} }
} }
}); });
@ -230,6 +240,52 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
fetchTrackData(); 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() { public void resetFetchTimestampToChosenDate() {
@ -263,12 +319,13 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
refresh(); refresh();
} }
private void showTrack(String gpxTrack) { private void showActivityDetail(int position){
try { Intent ActivitySummaryDetailIntent = new Intent(this, ActivitySummaryDetail.class);
AndroidUtils.viewFile(gpxTrack, Intent.ACTION_VIEW, this); ActivitySummaryDetailIntent.putExtra("position", position);
} catch (IOException e) { ActivitySummaryDetailIntent.putExtra("filter", activityFilter);
GB.toast(this, "Unable to display GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e); ActivitySummaryDetailIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice);
} startActivity(ActivitySummaryDetailIntent);
} }
private void fetchTrackData() { private void fetchTrackData() {
@ -300,4 +357,7 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
} }
} }
} }

View File

@ -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);
}
}

View File

@ -22,6 +22,7 @@ import android.text.format.DateFormat;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.CheckedTextView; import android.widget.CheckedTextView;
import android.widget.EditText;
import android.widget.TimePicker; import android.widget.TimePicker;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -46,6 +47,8 @@ public class AlarmDetails extends AbstractGBActivity {
private CheckedTextView cbFriday; private CheckedTextView cbFriday;
private CheckedTextView cbSaturday; private CheckedTextView cbSaturday;
private CheckedTextView cbSunday; private CheckedTextView cbSunday;
private EditText title;
private EditText description;
private GBDevice device; private GBDevice device;
@Override @Override
@ -56,6 +59,9 @@ public class AlarmDetails extends AbstractGBActivity {
alarm = (Alarm) getIntent().getSerializableExtra(nodomain.freeyourgadget.gadgetbridge.model.Alarm.EXTRA_ALARM); alarm = (Alarm) getIntent().getSerializableExtra(nodomain.freeyourgadget.gadgetbridge.model.Alarm.EXTRA_ALARM);
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE); 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); timePicker = findViewById(R.id.alarm_time_picker);
cbSmartWakeup = findViewById(R.id.alarm_cb_smart_wakeup); cbSmartWakeup = findViewById(R.id.alarm_cb_smart_wakeup);
cbSnooze = findViewById(R.id.alarm_cb_snooze); cbSnooze = findViewById(R.id.alarm_cb_snooze);
@ -126,6 +132,12 @@ public class AlarmDetails extends AbstractGBActivity {
int snoozeVisibility = supportsSnoozing() ? View.VISIBLE : View.GONE; int snoozeVisibility = supportsSnoozing() ? View.VISIBLE : View.GONE;
cbSnooze.setVisibility(snoozeVisibility); 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)); cbMonday.setChecked(alarm.getRepetition(Alarm.ALARM_MON));
cbTuesday.setChecked(alarm.getRepetition(Alarm.ALARM_TUE)); cbTuesday.setChecked(alarm.getRepetition(Alarm.ALARM_TUE));
cbWednesday.setChecked(alarm.getRepetition(Alarm.ALARM_WED)); cbWednesday.setChecked(alarm.getRepetition(Alarm.ALARM_WED));
@ -144,6 +156,14 @@ public class AlarmDetails extends AbstractGBActivity {
return false; return false;
} }
private boolean supportsDescription() {
if (device != null) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
return coordinator.supportsAlarmDescription(device);
}
return false;
}
private boolean supportsSnoozing() { private boolean supportsSnoozing() {
if (device != null) { if (device != null) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
@ -170,6 +190,8 @@ public class AlarmDetails extends AbstractGBActivity {
alarm.setRepetition(repetitionMask); alarm.setRepetition(repetitionMask);
alarm.setHour(timePicker.getCurrentHour()); alarm.setHour(timePicker.getCurrentHour());
alarm.setMinute(timePicker.getCurrentMinute()); alarm.setMinute(timePicker.getCurrentMinute());
alarm.setTitle(title.getText().toString());
alarm.setDescription(description.getText().toString());
DBHelper.store(alarm); DBHelper.store(alarm);
} }

View File

@ -146,7 +146,7 @@ public class ConfigureAlarms extends AbstractGBActivity {
} }
private Alarm createDefaultAlarm(@NonNull Device device, @NonNull User user, int position) { 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 @Override

View File

@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
import android.Manifest; import android.Manifest;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.NotificationManager;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -39,6 +40,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat; import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
@ -50,9 +52,11 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.navigation.NavigationView; import com.google.android.material.navigation.NavigationView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import de.cketti.library.changelog.ChangeLog; import de.cketti.library.changelog.ChangeLog;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -68,23 +72,19 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class ControlCenterv2 extends AppCompatActivity public class ControlCenterv2 extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, GBActivity { implements NavigationView.OnNavigationItemSelectedListener, GBActivity {
public static final int MENU_REFRESH_CODE = 1;
private static PhoneStateListener fakeStateListener;
//needed for KK compatibility //needed for KK compatibility
static { static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
} }
private DeviceManager deviceManager; private DeviceManager deviceManager;
private GBDeviceAdapterv2 mGBDeviceAdapter; private GBDeviceAdapterv2 mGBDeviceAdapter;
private RecyclerView deviceListView; private RecyclerView deviceListView;
private FloatingActionButton fab; private FloatingActionButton fab;
private boolean isLanguageInvalid = false; private boolean isLanguageInvalid = false;
public static final int MENU_REFRESH_CODE=1;
private static PhoneStateListener fakeStateListener;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@ -102,6 +102,7 @@ public class ControlCenterv2 extends AppCompatActivity
} }
} }
}; };
private boolean pesterWithPermissions = true;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -192,11 +193,16 @@ public class ControlCenterv2 extends AppCompatActivity
* Ask for permission to intercept notifications on first run. * Ask for permission to intercept notifications on first run.
*/ */
Prefs prefs = GBApplication.getPrefs(); Prefs prefs = GBApplication.getPrefs();
if (prefs.getBoolean("firstrun", true)) { pesterWithPermissions = prefs.getBoolean("permission_pestering", true);
prefs.getPreferences().edit().putBoolean("firstrun", false).apply();
Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); Set<String> set = NotificationManagerCompat.getEnabledListenerPackages(this);
startActivity(enableIntent); 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) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
checkAndRequestPermissions(); checkAndRequestPermissions();
} }
@ -205,7 +211,7 @@ public class ControlCenterv2 extends AppCompatActivity
if (cl.isFirstRun()) { if (cl.isFirstRun()) {
try { try {
cl.getLogDialog().show(); cl.getLogDialog().show();
} catch (Exception ignored){ } catch (Exception ignored) {
GB.toast(getBaseContext(), "Error showing Changelog", Toast.LENGTH_LONG, GB.ERROR); 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); GB.toast(getBaseContext(), "Error showing Changelog", Toast.LENGTH_LONG, GB.ERROR);
} }
return true; return true;
case R.id.about:
Intent aboutIntent = new Intent(this, AboutActivity.class);
startActivity(aboutIntent);
return true;
} }
return true; return true;
@ -343,8 +353,6 @@ public class ControlCenterv2 extends AppCompatActivity
wantedPermissions.add(Manifest.permission.READ_CONTACTS); wantedPermissions.add(Manifest.permission.READ_CONTACTS);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_DENIED) if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.CALL_PHONE); 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) if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_CALL_LOG); wantedPermissions.add(Manifest.permission.READ_CALL_LOG);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED) 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); wantedPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED) if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_CALENDAR); 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 { try {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.MEDIA_CONTENT_CONTROL) == PackageManager.PERMISSION_DENIED) if (ContextCompat.checkSelfPermission(this, Manifest.permission.MEDIA_CONTENT_CONTROL) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.MEDIA_CONTENT_CONTROL); wantedPermissions.add(Manifest.permission.MEDIA_CONTENT_CONTROL);
} catch (Exception ignored){ } catch (Exception ignored) {
} }
if (!wantedPermissions.isEmpty()) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0); 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 // HACK: On Lineage we have to do this so that the permission dialog pops up
if (fakeStateListener == null) { if (fakeStateListener == null) {

View File

@ -17,7 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@ -160,9 +159,8 @@ public class DbManagementActivity extends AbstractGBActivity {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME)); return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
} }
} } catch (Exception fdfsdfds) {
catch (Exception fdfsdfds) { LOG.error("Error", fdfsdfds);
LOG.warn("fuck");
} }
} }
return ""; return "";
@ -205,7 +203,7 @@ public class DbManagementActivity extends AbstractGBActivity {
} }
} }
} catch (Exception e) { } 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) { } catch (Exception ex) {
GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_error_importing_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, 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()) { try (DBHandler lockHandler = GBApplication.acquireDB()) {
List<Device> activeDevices = DBHelper.getActiveDevices(lockHandler.getDaoSession()); List<Device> activeDevices = DBHelper.getActiveDevices(lockHandler.getDaoSession());
for (Device dbDevice : activeDevices) { for (Device dbDevice : activeDevices) {

View File

@ -46,18 +46,20 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity;
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter; import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.ConfigActivity; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.ConfigActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity; import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
@ -392,8 +394,14 @@ public class SettingsActivity extends AbstractSettingsActivity {
newValues[0] = "default"; newValues[0] = "default";
int i = 1; int i = 1;
Set<String> existingNames = new HashSet<>();
for (ResolveInfo resolveInfo : mediaReceivers) { 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; newValues[i] = resolveInfo.activityInfo.packageName;
i++; i++;
} }

View File

@ -59,6 +59,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
public abstract class AbstractWeekChartFragment extends AbstractChartFragment { public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class); protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class);
protected final int TOTAL_DAYS = getRangeDays(); protected final int TOTAL_DAYS = getRangeDays();
protected int TOTAL_DAYS_FOR_AVERAGE = 0;
private Locale mLocale; private Locale mLocale;
private int mTargetValue = 0; private int mTargetValue = 0;
@ -124,10 +125,17 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
ArrayList<String> labels = new ArrayList<String>(); ArrayList<String> labels = new ArrayList<String>();
long balance = 0; long balance = 0;
long daily_balance=0;
TOTAL_DAYS_FOR_AVERAGE=0;
for (int counter = 0; counter < TOTAL_DAYS; counter++) { for (int counter = 0; counter < TOTAL_DAYS; counter++) {
ActivityAmounts amounts = getActivityAmountsForDay(db, day, device); 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))); entries.add(new BarEntry(counter, getTotalsForActivityAmounts(amounts)));
labels.add(getWeeksChartsLabel(day)); labels.add(getWeeksChartsLabel(day));
day.add(Calendar.DATE, 1); day.add(Calendar.DATE, 1);
@ -146,8 +154,8 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
barChart.getAxisLeft().addLimitLine(target); barChart.getAxisLeft().addLimitLine(target);
float average = 0; float average = 0;
if (TOTAL_DAYS > 0) { if (TOTAL_DAYS_FOR_AVERAGE > 0) {
average = Math.abs(balance / TOTAL_DAYS); average = Math.abs(balance / TOTAL_DAYS_FOR_AVERAGE);
} }
LimitLine average_line = new LimitLine(average); LimitLine average_line = new LimitLine(average);
average_line.setLabel(getString(R.string.average, getAverage(average))); average_line.setLabel(getString(R.string.average, getAverage(average)));

View File

@ -76,7 +76,7 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
@Override @Override
protected String getBalanceMessage(long balance, int targetValue) { protected String getBalanceMessage(long balance, int targetValue) {
if (balance > 0) { if (balance > 0) {
final long totalBalance = balance - ((long)targetValue * TOTAL_DAYS); final long totalBalance = balance - ((long)targetValue * TOTAL_DAYS_FOR_AVERAGE);
if (totalBalance > 0) if (totalBalance > 0)
return getString(R.string.overslept, getHM(totalBalance)); return getString(R.string.overslept, getHM(totalBalance));
else else

View File

@ -109,7 +109,7 @@ public class WeekStepsChartFragment extends AbstractWeekChartFragment {
@Override @Override
protected String getBalanceMessage(long balance, int targetValue) { protected String getBalanceMessage(long balance, int targetValue) {
if (balance > 0) { if (balance > 0) {
final long totalBalance = balance - ((long)targetValue * TOTAL_DAYS); final long totalBalance = balance - ((long)targetValue * TOTAL_DAYS_FOR_AVERAGE);
if (totalBalance > 0) if (totalBalance > 0)
return getString(R.string.overstep, Math.abs(totalBalance)); return getString(R.string.overstep, Math.abs(totalBalance));
else else

View File

@ -17,6 +17,7 @@
package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings; package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
public class DeviceSettingsPreferenceConst { public class DeviceSettingsPreferenceConst {
public static final String PREF_LANGUAGE = "language";
public static final String PREF_DATEFORMAT = "dateformat"; public static final String PREF_DATEFORMAT = "dateformat";
public static final String PREF_TIMEFORMAT = "timeformat"; public static final String PREF_TIMEFORMAT = "timeformat";
public static final String PREF_WEARLOCATION = "wearlocation"; 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_BUTTON_3_FUNCTION = "button_3_function";
public static final String PREF_VIBRATION_STRENGH_PERCENTAGE = "vibration_strength"; 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_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";
} }

View File

@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory;
import java.util.Objects; import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst; import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants; import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst; 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.XTimePreference;
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreferenceFragment; 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_1_FUNCTION;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_2_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_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_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_SCREEN_ORIENTATION;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE; 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_END;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ON_LIFT_START; 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_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;
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_END;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB_OFF; 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_MI2_DATEFORMAT);
addPreferenceHandlerFor(PREF_DATEFORMAT); addPreferenceHandlerFor(PREF_DATEFORMAT);
addPreferenceHandlerFor(PREF_DISPLAY_ITEMS); addPreferenceHandlerFor(PREF_DISPLAY_ITEMS);
addPreferenceHandlerFor(PREF_SHORTCUTS);
addPreferenceHandlerFor(PREF_LANGUAGE); addPreferenceHandlerFor(PREF_LANGUAGE);
addPreferenceHandlerFor(PREF_EXPOSE_HR_THIRDPARTY); addPreferenceHandlerFor(PREF_EXPOSE_HR_THIRDPARTY);
addPreferenceHandlerFor(PREF_WEARLOCATION); addPreferenceHandlerFor(PREF_WEARLOCATION);
@ -321,6 +343,19 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat {
addPreferenceHandlerFor(PREF_BUTTON_2_FUNCTION); addPreferenceHandlerFor(PREF_BUTTON_2_FUNCTION);
addPreferenceHandlerFor(PREF_BUTTON_3_FUNCTION); addPreferenceHandlerFor(PREF_BUTTON_3_FUNCTION);
addPreferenceHandlerFor(PREF_VIBRATION_STRENGH_PERCENTAGE); 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); String displayOnLiftState = prefs.getString(PREF_ACTIVATE_DISPLAY_ON_LIFT, PREF_DO_NOT_DISTURB_OFF);
boolean displayOnLiftScheduled = displayOnLiftState.equals(PREF_DO_NOT_DISTURB_SCHEDULED); 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(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(MakibesHR3Constants.PREF_FIND_PHONE_DURATION, InputType.TYPE_CLASS_NUMBER);
setInputTypeFor(DeviceSettingsPreferenceConst.PREF_RESERVER_ALARMS_CALENDAR, 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) { static DeviceSpecificSettingsFragment newInstance(String settingsFileSuffix, @NonNull int[] supportedSettings) {

View File

@ -59,6 +59,10 @@ public abstract class AbstractItemAdapter<T> extends ArrayAdapter<T> {
this.horizontalAlignment = horizontalAlignment; this.horizontalAlignment = horizontalAlignment;
} }
public void setActivityKindFilter(int activityKind){
this.setActivityKindFilter(activityKind);
}
@Override @Override
public View getView(int position, View view, ViewGroup parent) { public View getView(int position, View view, ViewGroup parent) {
T item = getItem(position); T item = getItem(position);

View File

@ -21,6 +21,7 @@ import android.widget.Toast;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import de.greenrobot.dao.query.QueryBuilder; import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -36,10 +37,12 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class ActivitySummariesAdapter extends AbstractItemAdapter<BaseActivitySummary> { public class ActivitySummariesAdapter extends AbstractItemAdapter<BaseActivitySummary> {
private final GBDevice device; private final GBDevice device;
private int activityKindFilter;
public ActivitySummariesAdapter(Context context, GBDevice device) { public ActivitySummariesAdapter(Context context, GBDevice device, int activityKindFilter) {
super(context); super(context);
this.device = device; this.device = device;
this.activityKindFilter = activityKindFilter;
loadItems(); loadItems();
} }
@ -50,7 +53,17 @@ public class ActivitySummariesAdapter extends AbstractItemAdapter<BaseActivitySu
Device dbDevice = DBHelper.findDevice(device, handler.getDaoSession()); Device dbDevice = DBHelper.findDevice(device, handler.getDaoSession());
QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder(); 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(); List<BaseActivitySummary> allSummaries = qb.build().list();
setItems(allSummaries, true); setItems(allSummaries, true);
} catch (Exception e) { } catch (Exception e) {
@ -58,23 +71,41 @@ public class ActivitySummariesAdapter extends AbstractItemAdapter<BaseActivitySu
} }
} }
public void setActivityKindFilter(int filter){
this.activityKindFilter=filter;
}
@Override @Override
protected String getName(BaseActivitySummary item) { protected String getName(BaseActivitySummary item) {
String name = item.getName(); String name = item.getName();
if (name != null && name.length() > 0) { if (name != null && name.length() > 0) {
return name; return name;
} }
Date startTime = item.getStartTime(); Date startTime = item.getStartTime();
Long duration = (item.getEndTime().getTime() - item.getStartTime().getTime());
if (startTime != null) { if (startTime != null) {
return DateTimeUtils.formatDateTime(startTime); return DateTimeUtils.formatDateTime(startTime) + " (" + DateTimeUtils.formatDurationHoursMinutes(duration, TimeUnit.MILLISECONDS) + ")";
} }
return "Unknown activity"; return "Unknown activity";
} }
@Override @Override
protected String getDetails(BaseActivitySummary item) { 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 @Override

View File

@ -16,6 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.adapter; package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context; import android.content.Context;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -26,9 +27,12 @@ import android.widget.TextView;
import java.util.List; import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
/** /**
@ -47,22 +51,37 @@ public class DeviceCandidateAdapter extends ArrayAdapter<GBDeviceCandidate> {
@Override @Override
public View getView(int position, View view, ViewGroup parent) { public View getView(int position, View view, ViewGroup parent) {
GBDeviceCandidate device = getItem(position); GBDeviceCandidate device = getItem(position);
if (view == null) { if (view == null) {
LayoutInflater inflater = (LayoutInflater) context LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.item_with_details, parent, false); view = inflater.inflate(R.layout.item_with_details, parent, false);
} }
ImageView deviceImageView = (ImageView) view.findViewById(R.id.item_image); ImageView deviceImageView = view.findViewById(R.id.item_image);
TextView deviceNameLabel = (TextView) view.findViewById(R.id.item_name); TextView deviceNameLabel = view.findViewById(R.id.item_name);
TextView deviceAddressLabel = (TextView) view.findViewById(R.id.item_details); TextView deviceAddressLabel = view.findViewById(R.id.item_details);
TextView deviceStatus = view.findViewById(R.id.item_status);
String name = formatDeviceCandidate(device); String name = formatDeviceCandidate(device);
deviceNameLabel.setText(name); deviceNameLabel.setText(name);
deviceAddressLabel.setText(device.getMacAddress()); deviceAddressLabel.setText(device.getMacAddress());
deviceImageView.setImageResource(device.getDeviceType().getIcon()); 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; return view;
} }

View File

@ -38,17 +38,21 @@ import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; 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.annotation.NonNull;
import androidx.cardview.widget.CardView; import androidx.cardview.widget.CardView;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.RecyclerView; 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.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesActivity; 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.VibrationActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsActivity; 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.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; 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.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
@ -70,6 +77,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
* Adapter for displaying GBDevice instances. * Adapter for displaying GBDevice instances.
*/ */
public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.ViewHolder> { public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.ViewHolder> {
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceAdapterv2.class);
private final Context context; private final Context context;
private List<GBDevice> deviceList; 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() { holder.calibrateDevice.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { 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); startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent); context.startActivity(startIntent);
} }
@ -352,9 +360,9 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { 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 // 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) { if (frequency < 87.5 || frequency > 108.0) {
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setTitle(R.string.pref_invalid_frequency_title) .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 @Override
@ -504,6 +553,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
ListView deviceInfoList; ListView deviceInfoList;
ImageView findDevice; ImageView findDevice;
ImageView removeDevice; ImageView removeDevice;
ImageView setAlias;
LinearLayout fmFrequencyBox; LinearLayout fmFrequencyBox;
TextView fmFrequencyLabel; TextView fmFrequencyLabel;
ImageView ledColor; ImageView ledColor;
@ -537,6 +587,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
deviceInfoList = view.findViewById(R.id.device_item_infos); deviceInfoList = view.findViewById(R.id.device_item_infos);
findDevice = view.findViewById(R.id.device_action_find); findDevice = view.findViewById(R.id.device_action_find);
removeDevice = view.findViewById(R.id.device_action_remove); 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); fmFrequencyBox = view.findViewById(R.id.device_fm_frequency_box);
fmFrequencyLabel = view.findViewById(R.id.fm_frequency); fmFrequencyLabel = view.findViewById(R.id.fm_frequency);
ledColor = view.findViewById(R.id.device_led_color); 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) { private String getUniqueDeviceName(GBDevice device) {
String deviceName = device.getName(); String deviceName = device.getAliasOrName();
if (!isUniqueDeviceName(device, deviceName)) { if (!isUniqueDeviceName(device, deviceName)) {
if (device.getModel() != null) { if (device.getModel() != null) {
deviceName = deviceName + " " + device.getModel(); deviceName = deviceName + " " + device.getModel();

View File

@ -429,6 +429,7 @@ public class DBHelper {
if (!isDeviceUpToDate(device, gbDevice)) { if (!isDeviceUpToDate(device, gbDevice)) {
device.setIdentifier(gbDevice.getAddress()); device.setIdentifier(gbDevice.getAddress());
device.setName(gbDevice.getName()); device.setName(gbDevice.getName());
device.setAlias(gbDevice.getAlias());
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice); DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
device.setManufacturer(coordinator.getManufacturer()); device.setManufacturer(coordinator.getManufacturer());
device.setType(gbDevice.getType().getKey()); device.setType(gbDevice.getType().getKey());
@ -449,6 +450,9 @@ public class DBHelper {
if (!Objects.equals(device.getName(), gbDevice.getName())) { if (!Objects.equals(device.getName(), gbDevice.getName())) {
return false; return false;
} }
if (!Objects.equals(device.getAlias(), gbDevice.getAlias())) {
return false;
}
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice); DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
if (!Objects.equals(device.getManufacturer(), coordinator.getManufacturer())) { if (!Objects.equals(device.getManufacturer(), coordinator.getManufacturer())) {
return false; return false;

View File

@ -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) {
}
}

View File

@ -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) {
}
}

View File

@ -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) {
}
}

View File

@ -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) {
}
}

View File

@ -17,11 +17,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices; package nodomain.freeyourgadget.gadgetbridge.devices;
import android.app.Activity;
import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanFilter;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -35,6 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst; 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.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao; import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
@ -65,7 +68,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
@Override @Override
public GBDevice createDevice(GBDeviceCandidate candidate) { 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 @Override
@ -152,6 +155,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return false; return false;
} }
@Override
public boolean supportsAlarmDescription(GBDevice device) {
return false;
}
@Override @Override
public boolean supportsMusicInfo() { public boolean supportsMusicInfo() {
return false; return false;
@ -180,4 +188,10 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
public int[] getSupportedDeviceSpecificSettings(GBDevice device) { public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return null; return null;
} }
@Nullable
@Override
public Class<? extends Activity> getCalibrationActivity() {
return null;
}
} }

View File

@ -128,6 +128,9 @@ public interface DeviceCoordinator {
@Nullable @Nullable
Class<? extends Activity> getPairingActivity(); Class<? extends Activity> getPairingActivity();
@Nullable
Class<? extends Activity> getCalibrationActivity();
/** /**
* Returns true if activity data fetching is supported by the device * Returns true if activity data fetching is supported by the device
* (with this coordinator). * (with this coordinator).
@ -207,6 +210,12 @@ public interface DeviceCoordinator {
*/ */
boolean supportsAlarmSnoozing(); 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. * Returns true if the given device supports heart rate measurements.
* @return * @return

View File

@ -49,6 +49,7 @@ public class HuamiConst {
public static final String MI_BAND3_NAME = "Mi Band 3"; 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_BAND3_NAME_2 = "Xiaomi Band 3";
public static final String MI_BAND4_NAME = "Mi Smart Band 4"; 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_ACTIVATE_DISPLAY_ON_LIFT = "activate_display_on_lift_wrist";
public static final String PREF_DISPLAY_ON_LIFT_START = "display_on_lift_start"; 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_DISCONNECT_NOTIFICATION_END = "disconnect_notification_end";
public static final String PREF_DISPLAY_ITEMS = "display_items"; 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_EXPOSE_HR_THIRDPARTY = "expose_hr_thirdparty";
public static final String PREF_USE_CUSTOM_FONT = "use_custom_font"; 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_DELAY = "button_action_broadcast_delay";
public static final String PREF_BUTTON_ACTION_BROADCAST = "button_action_broadcast"; 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) { public static int toActivityKind(int rawType) {
switch (rawType) { switch (rawType) {
case TYPE_DEEP_SLEEP: case TYPE_DEEP_SLEEP:

View File

@ -35,7 +35,6 @@ import java.text.SimpleDateFormat;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.Set;
import de.greenrobot.dao.query.QueryBuilder; import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; 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); 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) { public static boolean getUseCustomFont(String deviceAddress) {
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress); SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
return prefs.getBoolean(HuamiConst.PREF_USE_CUSTOM_FONT, false); return prefs.getBoolean(HuamiConst.PREF_USE_CUSTOM_FONT, false);

View File

@ -28,6 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper; import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareInfo;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
public abstract class HuamiFWHelper extends AbstractMiBandFWHelper { public abstract class HuamiFWHelper extends AbstractMiBandFWHelper {
protected HuamiFirmwareInfo firmwareInfo; protected HuamiFirmwareInfo firmwareInfo;
@ -110,6 +111,10 @@ public abstract class HuamiFWHelper extends AbstractMiBandFWHelper {
firmwareInfo.checkValid(); firmwareInfo.checkValid();
} }
@Override
public HuamiFirmwareType getFirmwareType() {
return firmwareInfo.getFirmwareType();
}
public HuamiFirmwareInfo getFirmwareInfo() { public HuamiFirmwareInfo getFirmwareInfo() {
return firmwareInfo; return firmwareInfo;
} }

View File

@ -89,6 +89,7 @@ public class AmazfitBipCoordinator extends HuamiCoordinator {
R.xml.devicesettings_sync_calendar, R.xml.devicesettings_sync_calendar,
R.xml.devicesettings_expose_hr_thirdparty, R.xml.devicesettings_expose_hr_thirdparty,
R.xml.devicesettings_buttonactions_with_longpress, R.xml.devicesettings_buttonactions_with_longpress,
R.xml.devicesettings_device_actions,
R.xml.devicesettings_pairingkey R.xml.devicesettings_pairingkey
}; };
} }

View File

@ -58,7 +58,8 @@ public class AmazfitBipSCoordinator extends HuamiCoordinator {
@Override @Override
public InstallHandler findInstallHandler(Uri uri, Context context) { public InstallHandler findInstallHandler(Uri uri, Context context) {
return null; AmazfitBipSFWInstallHandler handler = new AmazfitBipSFWInstallHandler(uri, context);
return handler.isValid() ? handler : null;
} }
@Override @Override
@ -76,18 +77,22 @@ public class AmazfitBipSCoordinator extends HuamiCoordinator {
return true; return true;
} }
@Override
public boolean supportsMusicInfo() {
return true;
}
@Override @Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) { public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return new int[]{ return new int[]{
R.xml.devicesettings_amazfitbip, R.xml.devicesettings_amazfitbips,
R.xml.devicesettings_timeformat, R.xml.devicesettings_timeformat,
R.xml.devicesettings_wearlocation, R.xml.devicesettings_wearlocation,
R.xml.devicesettings_custom_emoji_font, R.xml.devicesettings_custom_emoji_font,
R.xml.devicesettings_liftwrist_display, R.xml.devicesettings_liftwrist_display,
R.xml.devicesettings_disconnectnotification,
R.xml.devicesettings_sync_calendar, R.xml.devicesettings_sync_calendar,
R.xml.devicesettings_expose_hr_thirdparty, R.xml.devicesettings_expose_hr_thirdparty,
R.xml.devicesettings_buttonactions_with_longpress, R.xml.devicesettings_high_mtu,
R.xml.devicesettings_pairingkey R.xml.devicesettings_pairingkey
}; };
} }

View File

@ -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");
}
}
}

View File

@ -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;
}
}

View File

@ -88,7 +88,7 @@ public class AmazfitGTRCoordinator extends HuamiCoordinator {
} }
public int[] getSupportedDeviceSpecificSettings(GBDevice device) { public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return new int[]{ return new int[]{
R.xml.devicesettings_amazfitgtr, R.xml.devicesettings_amazfitgtsgtr,
R.xml.devicesettings_wearlocation, R.xml.devicesettings_wearlocation,
R.xml.devicesettings_timeformat, R.xml.devicesettings_timeformat,
R.xml.devicesettings_liftwrist_display, R.xml.devicesettings_liftwrist_display,

View File

@ -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
};
}
}

View File

@ -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");
}
}
}

View File

@ -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;
}
}

View File

@ -88,7 +88,7 @@ public class AmazfitGTSCoordinator extends HuamiCoordinator {
public int[] getSupportedDeviceSpecificSettings(GBDevice device) { public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return new int[]{ return new int[]{
R.xml.devicesettings_amazfitgtr, R.xml.devicesettings_amazfitgtsgtr,
R.xml.devicesettings_wearlocation, R.xml.devicesettings_wearlocation,
R.xml.devicesettings_timeformat, R.xml.devicesettings_timeformat,
R.xml.devicesettings_liftwrist_display, R.xml.devicesettings_liftwrist_display,

View File

@ -22,7 +22,6 @@ import android.net.Uri;
import java.io.IOException; import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.R; 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.AbstractMiBandFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@ -40,7 +39,7 @@ class AmazfitGTSFWInstallHandler extends AbstractMiBandFWInstallHandler {
@Override @Override
protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException { protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
return new AmazfitGTRFWHelper(uri, context); return new AmazfitGTSFWHelper(uri, context);
} }
@Override @Override

View File

@ -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
};
}
}

View File

@ -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");
}
}
}

View File

@ -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;
}
}

View File

@ -67,8 +67,19 @@ public class MiBand2FWInstallHandler extends AbstractMiBandFWInstallHandler {
Version v53 = MiBandConst.MI2_FW_VERSION_INTERMEDIATE_UPGRADE_53; Version v53 = MiBandConst.MI2_FW_VERSION_INTERMEDIATE_UPGRADE_53;
if (deviceVersion.compareTo(v53) < 0) { if (deviceVersion.compareTo(v53) < 0) {
String vInstall = getHelper().format(getHelper().getFirmwareVersion()); String vInstall = getHelper().format(getHelper().getFirmwareVersion());
if (vInstall == null || new Version(vInstall).compareTo(v53) > 0) { try {
String newInfoText = getContext().getString(R.string.mi2_fw_installhandler_fw53_hint, v53.get()) + "\n\n" + installActivity.getInfoText(); 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); installActivity.setInfoText(newInfoText);
} }
} }

View File

@ -112,6 +112,7 @@ public class MiBand3Coordinator extends HuamiCoordinator {
R.xml.devicesettings_swipeunlock, R.xml.devicesettings_swipeunlock,
R.xml.devicesettings_sync_calendar, R.xml.devicesettings_sync_calendar,
R.xml.devicesettings_expose_hr_thirdparty, R.xml.devicesettings_expose_hr_thirdparty,
R.xml.devicesettings_device_actions,
R.xml.devicesettings_pairingkey R.xml.devicesettings_pairingkey
}; };
} }

View File

@ -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;
}
}

View File

@ -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");
}
}
}

View File

@ -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;
}
}

View File

@ -17,18 +17,23 @@
*/ */
package nodomain.freeyourgadget.gadgetbridge.devices.itag; package nodomain.freeyourgadget.gadgetbridge.devices.itag;
import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.bluetooth.le.ScanFilter;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.Collection;
import java.util.Collections;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
@ -45,6 +50,27 @@ public class ITagCoordinator extends AbstractDeviceCoordinator {
return DeviceType.UNKNOWN; 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 @Override
public DeviceType getDeviceType() { public DeviceType getDeviceType() {
return DeviceType.ITAG; return DeviceType.ITAG;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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";
}

View File

@ -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();
}
}

View File

@ -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};
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -20,17 +20,20 @@ package nodomain.freeyourgadget.gadgetbridge.devices.miband;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.NonNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Locale;
import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; 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.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper; import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
@ -73,10 +76,12 @@ public abstract class AbstractMiBandFWHelper {
public abstract int getFirmware2Version(); public abstract int getFirmware2Version();
public static String formatFirmwareVersion(int version) { public static String formatFirmwareVersion(int version) {
if (version == -1) if (version == -1) {
return GBApplication.getContext().getString(R.string._unknown_); 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 >> 24 & 255,
version >> 16 & 255, version >> 16 & 255,
version >> 8 & 255, version >> 8 & 255,
@ -122,4 +127,6 @@ public abstract class AbstractMiBandFWHelper {
protected abstract void determineFirmwareInfo(byte[] wholeFirmwareBytes); protected abstract void determineFirmwareInfo(byte[] wholeFirmwareBytes);
public abstract void checkValid() throws IllegalArgumentException; public abstract void checkValid() throws IllegalArgumentException;
public abstract HuamiFirmwareType getFirmwareType();
} }

View File

@ -30,6 +30,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType.WATCHFACE;
public abstract class AbstractMiBandFWInstallHandler implements InstallHandler { public abstract class AbstractMiBandFWInstallHandler implements InstallHandler {
private static final Logger LOG = LoggerFactory.getLogger(AbstractMiBandFWInstallHandler.class); 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 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())); 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; return;
} }
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
if (helper.isSingleFirmware()) { if (helper.getFirmwareType() != WATCHFACE) {
getFwUpgradeNotice(); if (helper.isSingleFirmware()) {
builder.append(getFwUpgradeNotice()); getFwUpgradeNotice();
} else { builder.append(getFwUpgradeNotice());
builder.append(mContext.getString(R.string.fw_multi_upgrade_notice, helper.getHumanFirmwareVersion(), helper.getHumanFirmwareVersion2())); } else {
} builder.append(mContext.getString(R.string.fw_multi_upgrade_notice, helper.getHumanFirmwareVersion(), helper.getHumanFirmwareVersion2()));
}
if (helper.isFirmwareWhitelisted()) { if (helper.isFirmwareWhitelisted()) {
builder.append(" ").append(mContext.getString(R.string.miband_firmware_known)); builder.append(" ").append(mContext.getString(R.string.miband_firmware_known));
fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_compatible_version)); fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_compatible_version));
// TODO: set a CHECK (OKAY) button // TODO: set a CHECK (OKAY) button
} else { } else {
builder.append(" ").append(mContext.getString(R.string.miband_firmware_unknown_warning)).append(" \n\n") 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()))); .append(mContext.getString(R.string.miband_firmware_suggest_whitelist, String.valueOf(helper.getFirmwareVersion())));
fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_untested_version)); fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_untested_version));
// TODO: set a UNKNOWN (question mark) button // TODO: set a UNKNOWN (question mark) button
}
} }
installActivity.setInfoText(builder.toString()); installActivity.setInfoText(builder.toString());
installActivity.setInstallItem(fwItem); installActivity.setInstallItem(fwItem);

View File

@ -29,6 +29,7 @@ import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.AbstractMiFirmwareInfo; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.AbstractMiFirmwareInfo;
/** /**
@ -122,6 +123,11 @@ public class MiBandFWHelper extends AbstractMiBandFWHelper {
firmwareInfo.checkValid(); firmwareInfo.checkValid();
} }
@Override
public HuamiFirmwareType getFirmwareType() {
return null;
}
/** /**
* @param wholeFirmwareBytes * @param wholeFirmwareBytes
* @return * @return

View File

@ -27,12 +27,13 @@ import android.os.Bundle;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.List; import java.util.List;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import de.greenrobot.dao.query.Query; import de.greenrobot.dao.query.Query;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
@ -219,7 +220,7 @@ public class PebblePairingActivity extends AbstractGBActivity {
private void performConnect(GBDevice gbDevice) { private void performConnect(GBDevice gbDevice) {
if (gbDevice == null) { 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); GBApplication.deviceService().connect(gbDevice);
} }
@ -254,7 +255,7 @@ public class PebblePairingActivity extends AbstractGBActivity {
gbDevice = deviceHelper.toGBDevice(devices.get(0)); gbDevice = deviceHelper.toGBDevice(devices.get(0));
gbDevice.setVolatileAddress(btDevice.getAddress()); gbDevice.setVolatileAddress(btDevice.getAddress());
} catch (Exception e) { } 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(); returnToPairingActivity();
return null; return null;
} }

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -1,10 +1,14 @@
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid; package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
import android.annotation.SuppressLint;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.provider.MediaStore;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -26,6 +30,7 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -53,6 +58,9 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
SparseArray<String> widgetButtonsMapping = new SparseArray<>(4); SparseArray<String> widgetButtonsMapping = new SparseArray<>(4);
static public final String CONFIG_KEY_Q_ACTIONS = "Q_ACTIONS"; 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 @Override
protected void onCreate(Bundle savedInstanceState) { 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); Intent startIntent = new Intent(HRConfigActivity.this, WidgetSettingsActivity.class);
startIntent.putExtra("EXTRA_WIDGET", widget); startIntent.putExtra("EXTRA_WIDGET", widget);
startIntent.putExtra("EXTRA_WIDGET_IDNEX", position); 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(); loadCustomWidgetList();
@ -94,7 +103,36 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
public void onClick(View v) { public void onClick(View v) {
Intent startIntent = new Intent(HRConfigActivity.this, WidgetSettingsActivity.class); 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) { protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
if(data == null) return; if(data == null) return;
if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_CREATED) { if(requestCode == REQUEST_CODE_WIDGET_EDIT) {
CustomWidget widget = (CustomWidget) data.getExtras().get("EXTRA_WIDGET"); if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_CREATED) {
this.customWidgets.add(widget); CustomWidget widget = (CustomWidget) data.getExtras().get("EXTRA_WIDGET");
refreshWidgetList(); this.customWidgets.add(widget);
saveCustomWidgetList(); refreshWidgetList();
saveCustomWidgetList();
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS)); LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
} else if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_UPDATED) { } else if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_UPDATED) {
CustomWidget widget = (CustomWidget) data.getExtras().get("EXTRA_WIDGET"); CustomWidget widget = (CustomWidget) data.getExtras().get("EXTRA_WIDGET");
int updateIndex = data.getIntExtra("EXTRA_WIDGET_IDNEX", -1); 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(); String widgetJSON = sharedPreferences.getString("FOSSIL_HR_WIDGETS", "{}");
saveCustomWidgetList(); 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)); this.customWidgets.set(updateIndex, widget);
} else if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_DELETED){
int updateIndex = data.getIntExtra("EXTRA_WIDGET_IDNEX", -1);
this.customWidgets.remove(updateIndex); loadWidgetConfigs();
refreshWidgetList();
saveCustomWidgetList();
refreshWidgetList(); LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
saveCustomWidgetList(); } 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() { private void saveCustomWidgetList() {
@ -474,6 +536,7 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
super(HRConfigActivity.this, 0, objects); super(HRConfigActivity.this, 0, objects);
} }
@SuppressLint("ResourceType")
@NonNull @NonNull
@Override @Override
public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) { public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -85,7 +85,7 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
@Override @Override
public boolean supportsActivityTracking() { public boolean supportsActivityTracking() {
return false; return true;
} }
@Override @Override
@ -95,11 +95,15 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
@Override @Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) { public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return null; return new HybridHRActivitySampleProvider(device, session);
} }
@Override @Override
public InstallHandler findInstallHandler(Uri uri, Context context) { public InstallHandler findInstallHandler(Uri uri, Context context) {
if (isHybridHR()) {
FossilHRInstallHandler installHandler = new FossilHRInstallHandler(uri, context);
return installHandler.isValid() ? installHandler : null;
}
return null; return null;
} }
@ -121,6 +125,11 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
return this.supportsAlarmConfiguration() ? 5 : 0; return this.supportsAlarmConfiguration() ? 5 : 0;
} }
@Override
public boolean supportsAlarmDescription(GBDevice device) {
return isHybridHR();
}
@Override @Override
public boolean supportsSmartWakeup(GBDevice device) { public boolean supportsSmartWakeup(GBDevice device) {
return false; return false;
@ -128,7 +137,7 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
@Override @Override
public boolean supportsHeartRateMeasurement(GBDevice device) { public boolean supportsHeartRateMeasurement(GBDevice device) {
return false; return this.isHybridHR();
} }
@Override @Override

View File

@ -7,6 +7,8 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ListView; import android.widget.ListView;
import android.widget.RadioButton; import android.widget.RadioButton;

View File

@ -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;
}

View File

@ -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
};
}
}

View File

@ -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();
}
}

View File

@ -63,7 +63,9 @@ public class Watch9DeviceCoordinator extends AbstractDeviceCoordinator {
String deviceName = candidate.getName().toUpperCase(); String deviceName = candidate.getName().toUpperCase();
if (candidate.supportsService(Watch9Constants.UUID_SERVICE_WATCH9)) { if (candidate.supportsService(Watch9Constants.UUID_SERVICE_WATCH9)) {
return DeviceType.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; return DeviceType.WATCH9;
} else if (deviceName.equals("WATCH 9")) { } else if (deviceName.equals("WATCH 9")) {
return DeviceType.WATCH9; return DeviceType.WATCH9;
@ -161,4 +163,10 @@ public class Watch9DeviceCoordinator extends AbstractDeviceCoordinator {
public boolean supportsFindDevice() { public boolean supportsFindDevice() {
return false; return false;
} }
@Nullable
@Override
public Class<? extends Activity> getCalibrationActivity() {
return Watch9CalibrationActivity.class;
}
} }

View File

@ -80,6 +80,7 @@ public class ZeTimeConstants {
public static final byte CMD_REMINDERS = (byte) 0x97; public static final byte CMD_REMINDERS = (byte) 0x97;
public static final byte CMD_PUSH_CALENDAR_DAY = (byte) 0x99; public static final byte CMD_PUSH_CALENDAR_DAY = (byte) 0x99;
public static final byte CMD_MUSIC_CONTROL = (byte) 0xD0; 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; public static final byte CMD_TEST_SIGNALING = (byte) 0xFA;
// here are the action commands // here are the action commands
public static final byte CMD_REQUEST = (byte) 0x70; public static final byte CMD_REQUEST = (byte) 0x70;

View File

@ -124,7 +124,7 @@ public class ZeTimeCoordinator extends AbstractDeviceCoordinator {
@Override @Override
public boolean supportsCalendarEvents() { public boolean supportsCalendarEvents() {
return true; return false;
} }
@Override @Override
@ -163,8 +163,10 @@ public class ZeTimeCoordinator extends AbstractDeviceCoordinator {
@Override @Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) { public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return new int[]{ return new int[]{
R.xml.devicesettings_zetime,
R.xml.devicesettings_timeformat, R.xml.devicesettings_timeformat,
R.xml.devicesettings_wearlocation, R.xml.devicesettings_wearlocation,
R.xml.devicesettings_sync_calendar,
}; };
} }
} }

View File

@ -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;
}
}

View File

@ -48,7 +48,7 @@ public class BluetoothStateChangeReceiver extends BroadcastReceiver {
return; return;
} }
LOG.info("Bluetooth turned on => connecting..."); LOG.info("Bluetooth turned on (ACTION_STATE_CHANGED) => connecting...");
GBApplication.deviceService().connect(); GBApplication.deviceService().connect();
} else if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_OFF) { } else if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_OFF) {
LOG.info("Bluetooth turned off => disconnecting..."); LOG.info("Bluetooth turned off => disconnecting...");

View File

@ -101,7 +101,7 @@ public class CalendarReceiver extends BroadcastReceiver {
DaoSession session = dbHandler.getDaoSession(); DaoSession session = dbHandler.getDaoSession();
syncCalendar(eventList, session); syncCalendar(eventList, session);
} catch (Exception e1) { } 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);
} }
} }

View File

@ -33,6 +33,7 @@ import android.graphics.Color;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.media.MediaMetadata; import android.media.MediaMetadata;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.RemoteException; import android.os.RemoteException;
import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService;
@ -118,6 +119,10 @@ public class NotificationListener extends NotificationListenerService {
private long activeCallPostTime; private long activeCallPostTime;
private int mLastCallCommand = CallSpec.CALL_UNDEFINED; 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() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
@ -276,15 +281,18 @@ public class NotificationListener extends NotificationListenerService {
Prefs prefs = GBApplication.getPrefs(); Prefs prefs = GBApplication.getPrefs();
if (GBApplication.isRunningLollipopOrLater()) { if (GBApplication.isRunningLollipopOrLater()) {
if (NotificationCompat.CATEGORY_CALL.equals(sbn.getNotification().category) 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); handleCallNotification(sbn);
return; return;
} }
} }
if (shouldIgnoreNotification(sbn)) { if (shouldIgnoreNotification(sbn)) {
LOG.info("Ignoring notification"); if (!"com.sec.android.app.clockpackage".equals(sbn.getPackageName())) { // workaround to allow phone alarm notification
return; LOG.info("Ignore notification: " + sbn.getPackageName()); // need to fix
return;
}
} }
String source = sbn.getPackageName().toLowerCase(); String source = sbn.getPackageName().toLowerCase();
@ -460,7 +468,7 @@ public class NotificationListener extends NotificationListenerService {
private void handleCallNotification(StatusBarNotification sbn) { private void handleCallNotification(StatusBarNotification sbn) {
String app = sbn.getPackageName(); String app = sbn.getPackageName();
LOG.debug("got call from: " + app); 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"); LOG.debug("Ignoring non-voip call");
return; return;
} }
@ -616,8 +624,8 @@ public class NotificationListener extends NotificationListenerService {
* @return true if notification was handled, false otherwise * @return true if notification was handled, false otherwise
*/ */
public boolean handleMediaSessionNotification(MediaSessionCompat.Token mediaSession) { public boolean handleMediaSessionNotification(MediaSessionCompat.Token mediaSession) {
MusicSpec musicSpec = new MusicSpec(); final MusicSpec musicSpec = new MusicSpec();
MusicStateSpec stateSpec = new MusicStateSpec(); final MusicStateSpec stateSpec = new MusicStateSpec();
MediaControllerCompat c; MediaControllerCompat c;
try { try {
@ -660,8 +668,27 @@ public class NotificationListener extends NotificationListenerService {
musicSpec.trackNr = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER); musicSpec.trackNr = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER);
// finally, tell the device about it // finally, tell the device about it
GBApplication.deviceService().onSetMusicInfo(musicSpec); if (mSetMusicInfoRunnable != null) {
GBApplication.deviceService().onSetMusicState(stateSpec); 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; return true;
} catch (NullPointerException | RemoteException e) { } catch (NullPointerException | RemoteException e) {
@ -794,7 +821,7 @@ public class NotificationListener extends NotificationListenerService {
if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) { if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE); PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
if (powermanager != null && powermanager.isScreenOn()) { 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; return true;
} }
} }

View File

@ -26,12 +26,16 @@ import android.content.Intent;
import android.media.AudioManager; import android.media.AudioManager;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class PhoneCallReceiver extends BroadcastReceiver { public class PhoneCallReceiver extends BroadcastReceiver {
private static final Logger LOG = LoggerFactory.getLogger(PhoneCallReceiver.class);
private static int mLastState = TelephonyManager.CALL_STATE_IDLE; private static int mLastState = TelephonyManager.CALL_STATE_IDLE;
private static String mSavedNumber; private static String mSavedNumber;
@ -45,12 +49,16 @@ public class PhoneCallReceiver extends BroadcastReceiver {
mSavedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER"); mSavedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
} else if(intent.getAction().equals("nodomain.freeyourgadget.gadgetbridge.MUTE_CALL")) { } else if(intent.getAction().equals("nodomain.freeyourgadget.gadgetbridge.MUTE_CALL")) {
// Handle the mute request only if the phone is currently ringing // Handle the mute request only if the phone is currently ringing
if(mLastState != TelephonyManager.CALL_STATE_RINGING) if (mLastState != TelephonyManager.CALL_STATE_RINGING)
return; return;
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mLastRingerMode = audioManager.getRingerMode(); 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; mRestoreMutedCall = true;
} else { } else {
if (intent.hasExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)) { if (intent.hasExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)) {

View File

@ -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);
}
}
}
}
}

View File

@ -22,6 +22,10 @@ import android.content.Intent;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -30,9 +34,6 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; 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.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; 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_ADDR = "ADDR: ";
private static final String DEVINFO_ADDR2 = "ADDR2: "; private static final String DEVINFO_ADDR2 = "ADDR2: ";
private String mName; private String mName;
private String mAlias;
private final String mAddress; private final String mAddress;
private String mVolatileAddress; private String mVolatileAddress;
private final DeviceType mDeviceType; private final DeviceType mDeviceType;
@ -86,20 +88,22 @@ public class GBDevice implements Parcelable {
private int mNotificationIconDisconnected = R.drawable.ic_notification_disconnected; private int mNotificationIconDisconnected = R.drawable.ic_notification_disconnected;
private int mNotificationIconLowBattery = R.drawable.ic_notification_low_battery; private int mNotificationIconLowBattery = R.drawable.ic_notification_low_battery;
public GBDevice(String address, String name, DeviceType deviceType) { public GBDevice(String address, String name, String alias, DeviceType deviceType) {
this(address, null, name, 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; mAddress = address;
mVolatileAddress = address2; mVolatileAddress = address2;
mName = (name != null) ? name : mAddress; mName = (name != null) ? name : mAddress;
mAlias = alias;
mDeviceType = deviceType; mDeviceType = deviceType;
validate(); validate();
} }
private GBDevice(Parcel in) { private GBDevice(Parcel in) {
mName = in.readString(); mName = in.readString();
mAlias = in.readString();
mAddress = in.readString(); mAddress = in.readString();
mVolatileAddress = in.readString(); mVolatileAddress = in.readString();
mDeviceType = DeviceType.values()[in.readInt()]; mDeviceType = DeviceType.values()[in.readInt()];
@ -124,6 +128,7 @@ public class GBDevice implements Parcelable {
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName); dest.writeString(mName);
dest.writeString(mAlias);
dest.writeString(mAddress); dest.writeString(mAddress);
dest.writeString(mVolatileAddress); dest.writeString(mVolatileAddress);
dest.writeInt(mDeviceType.ordinal()); dest.writeInt(mDeviceType.ordinal());
@ -149,10 +154,22 @@ public class GBDevice implements Parcelable {
} }
} }
public String getName() { public String getName() {
return mName; return mName;
} }
public String getAlias() {
return mAlias;
}
public String getAliasOrName() {
if (mAlias != null && !mAlias.equals("")) {
return mAlias;
}
return mName;
}
public void setName(String name) { public void setName(String name) {
if (name == null) { if (name == null) {
LOG.warn("Ignoring setting of GBDevice name to null for " + this); LOG.warn("Ignoring setting of GBDevice name to null for " + this);
@ -161,6 +178,10 @@ public class GBDevice implements Parcelable {
mName = name; mName = name;
} }
public void setAlias(String alias) {
mAlias = alias;
}
public String getAddress() { public String getAddress() {
return mAddress; return mAddress;
} }

View File

@ -19,27 +19,33 @@ package nodomain.freeyourgadget.gadgetbridge.model;
import android.content.Context; import android.content.Context;
import androidx.annotation.DrawableRes;
import java.util.Arrays; import java.util.Arrays;
import androidx.annotation.DrawableRes;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
public class ActivityKind { public class ActivityKind {
public static final int TYPE_NOT_MEASURED = -1; public static final int TYPE_NOT_MEASURED = -1;
public static final int TYPE_UNKNOWN = 0; public static final int TYPE_UNKNOWN = 0x00000000;
public static final int TYPE_ACTIVITY = 1; public static final int TYPE_ACTIVITY = 0x00000001;
public static final int TYPE_LIGHT_SLEEP = 2; public static final int TYPE_LIGHT_SLEEP = 0x00000002;
public static final int TYPE_DEEP_SLEEP = 4; public static final int TYPE_DEEP_SLEEP = 0x00000004;
public static final int TYPE_NOT_WORN = 8; public static final int TYPE_NOT_WORN = 0x00000008;
public static final int TYPE_RUNNING = 16; public static final int TYPE_RUNNING = 0x00000010;
public static final int TYPE_WALKING = 32; public static final int TYPE_WALKING = 0x00000020;
public static final int TYPE_SWIMMING = 64; public static final int TYPE_SWIMMING = 0x00000040;
public static final int TYPE_CYCLING = 128; public static final int TYPE_CYCLING = 0x00000080;
public static final int TYPE_TREADMILL = 256; public static final int TYPE_TREADMILL = 0x00000100;
public static final int TYPE_EXERCISE = 512; 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_SLEEP = TYPE_LIGHT_SLEEP | TYPE_DEEP_SLEEP;
public static final int TYPE_ALL = TYPE_ACTIVITY | TYPE_SLEEP | TYPE_NOT_WORN; 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) { if ((types & ActivityKind.TYPE_EXERCISE) != 0) {
result[i++] = provider.toRawActivityKind(TYPE_EXERCISE); 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); return Arrays.copyOf(result, i);
} }
@ -104,6 +125,16 @@ public class ActivityKind {
return context.getString(R.string.activity_type_treadmill); return context.getString(R.string.activity_type_treadmill);
case TYPE_EXERCISE: case TYPE_EXERCISE:
return context.getString(R.string.activity_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: case TYPE_UNKNOWN:
default: default:
return context.getString(R.string.activity_type_unknown); return context.getString(R.string.activity_type_unknown);
@ -127,10 +158,19 @@ public class ActivityKind {
return R.drawable.ic_activity_biking; return R.drawable.ic_activity_biking;
case TYPE_TREADMILL: case TYPE_TREADMILL:
return R.drawable.ic_activity_walking; return R.drawable.ic_activity_walking;
case TYPE_EXERCISE: // fall through case TYPE_EXERCISE:
return R.drawable.ic_activity_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; 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_NOT_WORN: // fall through
case TYPE_ACTIVITY: // fall through case TYPE_ACTIVITY: // fall through
case TYPE_UNKNOWN: // fall through case TYPE_UNKNOWN: // fall through

View File

@ -16,6 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.model; package nodomain.freeyourgadget.gadgetbridge.model;
import org.json.JSONObject;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
@ -25,6 +27,7 @@ import java.util.Date;
* // TODO: split into separate entities? * // TODO: split into separate entities?
*/ */
public interface ActivitySummary extends Serializable { public interface ActivitySummary extends Serializable {
Long getId();
String getName(); String getName();
Date getStartTime(); Date getStartTime();
Date getEndTime(); Date getEndTime();
@ -35,6 +38,7 @@ public interface ActivitySummary extends Serializable {
long getDeviceId(); long getDeviceId();
long getUserId(); long getUserId();
String getSummaryData();
// long getSteps(); // long getSteps();
// float getDistanceMeters(); // float getDistanceMeters();
// float getAscentMeters(); // float getAscentMeters();

View File

@ -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;
}
}

View File

@ -52,4 +52,8 @@ public interface Alarm extends Serializable {
int getHour(); int getHour();
int getMinute(); int getMinute();
String getTitle();
String getDescription();
} }

View File

@ -104,7 +104,7 @@ public class DailyTotals {
} }
private long getTotalsStepsForActivityAmounts(ActivityAmounts activityAmounts) { public long getTotalsStepsForActivityAmounts(ActivityAmounts activityAmounts) {
long totalSteps = 0; long totalSteps = 0;
for (ActivityAmount amount : activityAmounts.getAmounts()) { 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); return getAllSamples(db, device, tsFrom, tsTo);
} }

View File

@ -35,15 +35,18 @@ public enum DeviceType {
PEBBLE(1, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled, R.string.devicetype_pebble), 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), 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), 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), 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), 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), 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), 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), 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_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_amazfit_gtr), AMAZFITGTR(18, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_amazfit_gtr),
AMAZFITGTS(19, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_amazfit_gts), 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_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_amazfit_bips), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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); TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
private final int key; private final int key;
@DrawableRes @DrawableRes
private final int defaultIcon; private final int defaultIcon;

View File

@ -63,6 +63,7 @@ import nodomain.freeyourgadget.gadgetbridge.externalevents.PebbleReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.PhoneCallReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.PhoneCallReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.SMSReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.SMSReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.TinyWeatherForecastGermanyReceiver;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
@ -197,6 +198,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
private CalendarReceiver mCalendarReceiver = null; private CalendarReceiver mCalendarReceiver = null;
private CMWeatherReceiver mCMWeatherReceiver = null; private CMWeatherReceiver mCMWeatherReceiver = null;
private LineageOsWeatherReceiver mLineageOsWeatherReceiver = null; private LineageOsWeatherReceiver mLineageOsWeatherReceiver = null;
private TinyWeatherForecastGermanyReceiver mTinyWeatherForecastGermanyReceiver = null;
private OmniJawsObserver mOmniJawsObserver = null; private OmniJawsObserver mOmniJawsObserver = null;
private final String[] mMusicActions = { private final String[] mMusicActions = {
@ -735,25 +737,35 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
filter.addAction(AlarmClockReceiver.GOOGLE_CLOCK_ALARM_DONE_ACTION); filter.addAction(AlarmClockReceiver.GOOGLE_CLOCK_ALARM_DONE_ACTION);
registerReceiver(mAlarmClockReceiver, filter); 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(); // Weather receivers
registerReceiver(mLineageOsWeatherReceiver, new IntentFilter("GB_UPDATE_WEATHER")); if ( coordinator != null && coordinator.supportsWeather()) {
} if (GBApplication.isRunningOreoOrLater()) {
} if (mLineageOsWeatherReceiver == null) {
if (mOmniJawsObserver == null && coordinator != null && coordinator.supportsWeather()) { mLineageOsWeatherReceiver = new LineageOsWeatherReceiver();
try { registerReceiver(mLineageOsWeatherReceiver, new IntentFilter("GB_UPDATE_WEATHER"));
mOmniJawsObserver = new OmniJawsObserver(new Handler()); }
getContentResolver().registerContentObserver(OmniJawsObserver.WEATHER_URI, true, mOmniJawsObserver); }
} catch (PackageManager.NameNotFoundException e) { else {
//Nothing wrong, it just means we're not running on omnirom. 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) && if (GBApplication.getPrefs().getBoolean("auto_fetch_enabled", false) &&
coordinator != null && coordinator.supportsActivityDataFetching() && mGBAutoFetchReceiver == null) { coordinator != null && coordinator.supportsActivityDataFetching() && mGBAutoFetchReceiver == null) {
mGBAutoFetchReceiver = new GBAutoFetchReceiver(); mGBAutoFetchReceiver = new GBAutoFetchReceiver();
@ -807,6 +819,11 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
} }
if (mOmniJawsObserver != null) { if (mOmniJawsObserver != null) {
getContentResolver().unregisterContentObserver(mOmniJawsObserver); getContentResolver().unregisterContentObserver(mOmniJawsObserver);
mOmniJawsObserver = null;
}
if (mTinyWeatherForecastGermanyReceiver != null) {
unregisterReceiver(mTinyWeatherForecastGermanyReceiver);
mTinyWeatherForecastGermanyReceiver = null;
} }
if (mGBAutoFetchReceiver != null) { if (mGBAutoFetchReceiver != null) {
unregisterReceiver(mGBAutoFetchReceiver); unregisterReceiver(mGBAutoFetchReceiver);

Some files were not shown because too many files have changed in this diff Show More