Merge branch 'master' into master
1
.github/ISSUE_TEMPLATE.md
vendored
@ -1,6 +1,7 @@
|
||||
#### Before opening an issue please confirm the following:
|
||||
- [ ] I have read the [wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] I have searched the [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] If you upload an image or other content, please make sure you have read and understood the [github policies and terms of services](https://help.github.com/articles/github-terms-of-service/#1-responsibility-for-user-generated-content)
|
||||
|
||||
#### Your issue is:
|
||||
*In case of a bug, do not forget to attach logs!*
|
||||
|
234
CHANGELOG.md
@ -1,11 +1,55 @@
|
||||
###Changelog
|
||||
### Changelog
|
||||
|
||||
###Version 0.19.1
|
||||
#### Version 0.20.2 (next)
|
||||
* Amazfit Bip: Various fixes regarding weather, add condition string support for FW 0.0.8.74
|
||||
* Amazfit Bip: enable caller display in later firmwares
|
||||
* Amazfit Bip: initial firmware update support (EXPERIMENTAL, AT YOUR OWN RISK)
|
||||
|
||||
#### Version 0.20.1
|
||||
* Amazfit Bip: Support icons and text body for notifications
|
||||
* Mi Band: Fix setting smart alarms
|
||||
|
||||
#### Version 0.20.0
|
||||
* Inital Amazfit Bip support (WIP)
|
||||
* Various theming fixes
|
||||
* Add workaround for blacklist not properly persisting
|
||||
* Handle resetting language to default properly
|
||||
* Pebble: Pass booleans from Javascript Appmessage correctly
|
||||
* Pebble: Make local configuration pages work on most recent webview implementation
|
||||
* Pebble: Allow to blacklist calendars
|
||||
* Add Greek and German transliteration support
|
||||
* Various visual improvements to charts
|
||||
|
||||
#### Version 0.19.4
|
||||
* Replace or relicense CC-NC licensed icons to satisfy F-Droid
|
||||
* Mi Band 2: Make infos to display on the Band configurable
|
||||
* Mi Band 2: Support wrist rotation to switch info setting
|
||||
* Mi Band 2: Support goal notification setting
|
||||
* Mi Band 2: Support do not disturb setting
|
||||
* Mi Band 2: Support inactivity warning setting
|
||||
|
||||
#### Version 0.19.3
|
||||
* Pebble: Fix crash when calendar access permission has been denied
|
||||
* Pebble: Fix wrong timestamps with Morpheuz running on Firmware >=3
|
||||
* Mi Band 2: Improve reliability when fetching activity data
|
||||
* HPlus: Fix intensity calculation without continuous connectivity
|
||||
* HPlus: Fix Unicode handling
|
||||
* HPlus: Initial not work detection
|
||||
* Fix memory leak
|
||||
* Only show Realtime Chart on devices supporting it
|
||||
|
||||
#### Version 0.19.2
|
||||
* Pebble: Fix recurring calendar events only appearing once per week
|
||||
* HPlus: Fix crash when receiving calls without phone number
|
||||
* HPlus: Detect unicode support on Zeband Plus
|
||||
* No longer quit Gadgetbridge when bluetooth gets turned off
|
||||
|
||||
#### Version 0.19.1
|
||||
* Fix crash at startup
|
||||
* HPlus: Improve reconnection to device
|
||||
* Improve transliteration
|
||||
|
||||
###Version 0.19.0
|
||||
#### Version 0.19.0
|
||||
* Pebble: allow calendar sync with Timeline (Title, Location, Description)
|
||||
* Pebble: display calendar icon for reminders from AOSP Calendar
|
||||
* HPlus: try to fix latin characters showing as random Chinese text
|
||||
@ -13,7 +57,7 @@
|
||||
* Improve generic notification reliability by trying to restart the notification listener when stale/crashed
|
||||
* Other small bugfixes
|
||||
|
||||
###Version 0.18.5
|
||||
#### Version 0.18.5
|
||||
* Applied some material design guidelines to Charts and (pebble) app management
|
||||
* Changed colours: deep sleep is now dark blue, light sleep is now light blue
|
||||
* Support for exporting and importing of preferences in addition to the database
|
||||
@ -24,24 +68,24 @@
|
||||
* HPlus: users can now decide whether they want to pair the device or not, hopefully fixing some connection problems (#642)
|
||||
* HPlus: display battery state and warn on low battery
|
||||
|
||||
###Version 0.18.4
|
||||
#### Version 0.18.4
|
||||
* Mi Band 2: Display realtime steps in Live Activity
|
||||
* Mi Band: Attempt to recognize Mi Band model with hwVersion = 8
|
||||
* Alarms activity improvements and fixes
|
||||
* Make Buttons in the main activity easier to hit
|
||||
|
||||
###Version 0.18.3
|
||||
#### Version 0.18.3
|
||||
* Fix bug that caused the same value in weekly charts for every day on Android 6 and older
|
||||
|
||||
###Version 0.18.2
|
||||
#### Version 0.18.2
|
||||
* Mi Band 2: Fix crash on "chat" or "social network" text notification (#603)
|
||||
|
||||
###Version 0.18.1
|
||||
#### Version 0.18.1
|
||||
* Pebble: Fix Firmware insstallation on Pebble Time Round (broken since 0.16.0)
|
||||
* Start VibrationActivity when using "find device" button with Vibratissimo
|
||||
* Support material fork of K9
|
||||
|
||||
###Version 0.18.0
|
||||
#### Version 0.18.0
|
||||
* All new GUI for the control center
|
||||
* Add Portuguese pt_PT and pt_BR translations
|
||||
* Add Czech translation
|
||||
@ -57,13 +101,13 @@
|
||||
* Mi Band 2: Set 12h/24h time format, following the Android configuration (#573)
|
||||
* Improved BLE discovery and connectivity
|
||||
|
||||
####Version 0.17.5
|
||||
#### Version 0.17.5
|
||||
* Automatically start the service on boot (can be turned off)
|
||||
* Pebble: PebbleKit compatibility improvements (Datalogging)
|
||||
* Pebble: Display music shuffle and repeat states for some players
|
||||
* Pebble 2/LE: Speed up data transfer
|
||||
|
||||
####Version 0.17.4
|
||||
#### Version 0.17.4
|
||||
* Better integration with android music players
|
||||
* Privacy options for calls (hide caller name/number)
|
||||
* Send a notification to the connected if the Android Alarm Clock rings (com.android.deskclock)
|
||||
@ -76,23 +120,23 @@
|
||||
* HPlus: Add device specific preferences and icon
|
||||
* HPlus: Support for Makibes F68
|
||||
|
||||
####Version 0.17.3
|
||||
#### Version 0.17.3
|
||||
* HPlus: Improve display of new messages and phone calls
|
||||
* HPlus: Fix bug related to steps and heart rate
|
||||
* Pebble: Support dynamic keys for natively supported watchfaces and watchapps (more stability accross versions)
|
||||
* Pebble: Fix error Toast being displayed when TimeStyle watchface is not installed
|
||||
* Mi Band 1+2: Support for connecting wihout BT pairing (workaround for certain connection problems)
|
||||
|
||||
####Version 0.17.2
|
||||
#### Version 0.17.2
|
||||
* Pebble: Fix temperature unit in Timestyle Pebble watchface
|
||||
* Add optional Cyrillic transliteration (for devices lacking the font)
|
||||
|
||||
####Version 0.17.1
|
||||
#### Version 0.17.1
|
||||
* Pebble: Fix installation of some watchapps
|
||||
* Pebble: Try to improve PebbleKit compatibility
|
||||
* HPlus: Fix bug setting current date
|
||||
|
||||
####Version 0.17.0
|
||||
#### Version 0.17.0
|
||||
* Add weather support through "Weather Notification" app
|
||||
* Various fixes for K9 mail when using the generic notification receiver
|
||||
* Add a preference to hide the persistent notification icon of Gadgetbridge
|
||||
@ -110,7 +154,7 @@
|
||||
* HPlus: Experimental synchronization of activity data (only sleep, steps and intensity)
|
||||
* HPlus: Fix some disconnection issues
|
||||
|
||||
####Version 0.16.0
|
||||
#### Version 0.16.0
|
||||
* New devices: HPlus (e.g. Zeblaze ZeBand), contributed by João Paulo Barraca
|
||||
* ZeBand: Initial support: notifications, heart rate, sleep monitoring, user configuration, date+time
|
||||
* Pebble 2: Fix Pebble Classic FW 3.x app variant being prioritized over native Pebble 2 app variant
|
||||
@ -121,15 +165,15 @@
|
||||
* Support sharing firmwares/watchapps/watchfaces to Gadgetbridge
|
||||
* Support for the "Subsonic" music player (#474)
|
||||
|
||||
####Version 0.15.2
|
||||
#### Version 0.15.2
|
||||
* Mi Band: Fix crash with unknown notification sources
|
||||
|
||||
####Version 0.15.1
|
||||
#### Version 0.15.1
|
||||
* Improved handling of notifications for some apps
|
||||
* Pebble 2/LE: Add setting to limit GATT MTU for debugging broken BLE stacks
|
||||
* Mi Band 2: Display battery status
|
||||
|
||||
####Version 0.15.0
|
||||
#### Version 0.15.0
|
||||
* New device: Liveview
|
||||
* Liveview: initial support (set the time and receive notifications)
|
||||
* Pebble: log pebble app logs if option is enabled in pebble development settings
|
||||
@ -137,20 +181,20 @@
|
||||
* Pebble: Further improve compatibility for watchface configuration
|
||||
* Mi Band 2: Initial support for firmware update (tested so far: 1.0.0.39)
|
||||
|
||||
####Version 0.14.4
|
||||
#### Version 0.14.4
|
||||
* Pebble 2/LE: Fix multiple bugs in reconnection code, honor reconnect tries from settings
|
||||
* Mi Band 2: Experimental support for activity recognition
|
||||
* Mi Band 2: Fix time setting code
|
||||
|
||||
####Version 0.14.3
|
||||
#### Version 0.14.3
|
||||
* Pebble: Experimental support for pairing and using all Pebble models via BLE
|
||||
* Mi Band 1: Fix regression causing display of wrong activity data (#440)
|
||||
* Mi Band 2: Support for continuous heart rate measurements in live activity view
|
||||
|
||||
####Version 0.14.2
|
||||
#### Version 0.14.2
|
||||
* Pebble 2: Fix a bug where the Pebble got disconnected by other unrelated LE devices
|
||||
|
||||
####Version 0.14.1
|
||||
#### Version 0.14.1
|
||||
* Mi Band 2: Initial experimental support for activity data
|
||||
* Mi Band 2: Send the fitness goal (steps) to the band
|
||||
* Pebble 2: Work around firmware installation issues (tested with upgrading 4.2 to 4.3)
|
||||
@ -158,7 +202,7 @@
|
||||
* Pebble: add Kickstart watch face to app manager on FW 4.x
|
||||
* Charts: display the total time range, not just the range with available data
|
||||
|
||||
####Version 0.14.0
|
||||
#### Version 0.14.0
|
||||
* Pebble 2: Initial experimental support for P2/PT2 using BLE
|
||||
* Pebble: Special support in device discovery activity (MUST be used to get Pebble 2 working)
|
||||
* Pebble: Improve compatibility for watchface configuration
|
||||
@ -167,16 +211,16 @@
|
||||
* Mi Band 2: configuration option to display the time + date or just the time
|
||||
* Mi Band 2: honor the wear location configuration option
|
||||
|
||||
####Version 0.13.9
|
||||
#### Version 0.13.9
|
||||
* Pebble: use the last known location for setting sunrise and sunset
|
||||
* Pebble: fix Health disappearing forever when deactivating through app manager (and get it back for affected users)
|
||||
* Mi Band 2: More fixes for connection issues (#408)
|
||||
|
||||
####Version 0.13.8
|
||||
#### Version 0.13.8
|
||||
* Mi Band 2: fix connection issues for users of Mi Fit (#408, #425)
|
||||
* Mi Band 1A: fix firmware update for certain 1A models
|
||||
|
||||
####Version 0.13.7
|
||||
#### Version 0.13.7
|
||||
* Pebble: Fix configuration of certain pebble apps (eg. QR Generator, Squared 4.0)
|
||||
* Pebble: Add context menu option in app manager to search a watchapp in the pebble appstore
|
||||
* Mi Band: allow to delete Mi Band address from development settings
|
||||
@ -185,16 +229,16 @@
|
||||
* Attempt to fix spurious device discovery problems
|
||||
* Correctly recognize Toffeed, Slimsocial and MaterialFBook as facebook notification sources
|
||||
|
||||
####Version 0.13.6
|
||||
#### Version 0.13.6
|
||||
* Mi Band 2: Support for multiple alarms (3 at the moment)
|
||||
* Mi Band 2: Fix for alarms not working when just one is enabled
|
||||
|
||||
####Version 0.13.5
|
||||
#### Version 0.13.5
|
||||
* Mi Band 2: Support setting one alarm
|
||||
* Pebble: Health compatibility for Firmware 4.2
|
||||
* Improve support for K9 when generic notifications are used (K9 notifications set to never)
|
||||
|
||||
####Version 0.13.4
|
||||
#### Version 0.13.4
|
||||
* Mi Band: Initial support for recording heart and displaying rate values
|
||||
* Mi Band: Support for testing vibration patterns directly from the preferences
|
||||
* Mi Band: Clean up vibration preferences
|
||||
@ -205,36 +249,36 @@
|
||||
* Pebble: new icons and colours for certain apps
|
||||
* Debug-screen: added button to test "new functionality", currently live sensor data for Mi Band 1
|
||||
|
||||
####Version 0.13.3
|
||||
#### Version 0.13.3
|
||||
* Fix regressions with missing bars and labels in charts
|
||||
* Allow to set notification type in Debug activity
|
||||
* Move "Disconnect" back to the bottom of the context menu
|
||||
* Mi Band 2: Display Message and Phone icons
|
||||
|
||||
####Version 0.13.2
|
||||
#### Version 0.13.2
|
||||
* Support deleting devices (and their data) in control center
|
||||
* Sort devices lexicographically in control center
|
||||
* Do not forward group summary notifications (could fix some duplicate notifications)
|
||||
* Pebble: Support for health on FW 4.1
|
||||
* Mi Band: Fix offline charts not displaying heartrate for Mi 1S
|
||||
|
||||
####Version 0.13.1
|
||||
#### Version 0.13.1
|
||||
* Improved BLE scanning for Android 5.0+
|
||||
* Pebble: try to work around duplicate Telegram messages and support Telegram icon
|
||||
* Pebble: fix some incompatibilities with certain PebbleKit Android apps
|
||||
|
||||
####Version 0.13.0
|
||||
#### Version 0.13.0
|
||||
* Initial working Mi Band 2 support (only notifications, no activity and heart rate support)
|
||||
* Experimental support for Vibratissimo devices
|
||||
|
||||
####Version 0.12.2
|
||||
#### Version 0.12.2
|
||||
* Fix for user attribute database table getting spammed and store sleep and steps goals properly
|
||||
|
||||
####Version 0.12.1 (release withdrawn)
|
||||
#### Version 0.12.1 (release withdrawn)
|
||||
* Pebble: Fix activity data being associated with the wrong device and/or user in some cases causing them to invisible in charts
|
||||
* Remove special handling for Conversations notifications since upstream dropped special pebble support
|
||||
|
||||
####Version 0.12.0 (release withdrawn)
|
||||
#### Version 0.12.0 (release withdrawn)
|
||||
* NB: User action needed to migrate existing data!
|
||||
* Store activity data per device and provider to allow multiple devices of the same kind with separate data. Migration is available, except for Pebble Misfit data. Existing data from multiple devices of the same kind (eg. multiple Mi Bands) will get merged while importing.
|
||||
* In Control Center, display known devices even when Bluetooth is off
|
||||
@ -243,10 +287,10 @@
|
||||
* Pebble: Optionally allow raw Pebble Health data to be stored in database completely (for later interpretation, when we are able to decode it)
|
||||
* Mi Band: fix displaying of deep sleep vs. light sleep (was inverted)
|
||||
|
||||
####Version 0.11.2
|
||||
#### Version 0.11.2
|
||||
* Mi Band: support for devices that cannot pair with the band (#349)
|
||||
|
||||
####Version 0.11.1
|
||||
#### Version 0.11.1
|
||||
* Various fixes (including crashes) for location settings
|
||||
* Pebble: Support Pebble Time 2 emulator (needs recompilation of Gadgetbridge)
|
||||
* Fix a rare crash when, due to Bluetooth problems, when a device has no name
|
||||
@ -259,19 +303,19 @@
|
||||
* Charts: only display heart rate samples on devices that support that
|
||||
* Add more logging to detect problems with external directories (#343)
|
||||
|
||||
####Version 0.11.0
|
||||
#### Version 0.11.0
|
||||
* Pebble: new App Manager (keeps track of installed apps and allows app sorting on FW 3.x)
|
||||
* Pebble: call dismissal with canned SMS (FW 3.x)
|
||||
* Pebble: watchapp configuration presets
|
||||
* Pebble: fix regression with FW 2.x (almost everything was broken in 0.10.2)
|
||||
|
||||
####Version 0.10.2
|
||||
#### Version 0.10.2
|
||||
* Pebble: allow to manually paste configuration data for legacy configuration pages
|
||||
* Pebble: various improvements to the configuration page
|
||||
* Pebble: Support FW 4.0-dp1 and Pebble2 emulator (needs recompilation of Gadgetbridge)
|
||||
* Pebble: Fix a problem with key events when using the Pebble music player
|
||||
|
||||
####Version 0.10.1
|
||||
#### Version 0.10.1
|
||||
* Pebble: set extended music info by dissecting notifications on Android 5.0+
|
||||
* Pebble: various other improvements to music playback
|
||||
* Pebble: allow ignoring activity trackers individually (to keep the data on the pebble)
|
||||
@ -279,7 +323,7 @@
|
||||
* Mi Band: initial and untested support for Mi Band 2
|
||||
* Allow setting the application language
|
||||
|
||||
####Version 0.10.0
|
||||
#### Version 0.10.0
|
||||
* Pebble: option to send sunrise and sunset events to timeline
|
||||
* Pebble: fix problems with unknown app keys while configuring watchfaces
|
||||
* Mi Band: BLE connection fixes
|
||||
@ -287,7 +331,7 @@
|
||||
* Re-enable device paring activity on Android 6 (BLE scanning needs the location preference)
|
||||
* Display device address in device info
|
||||
|
||||
####Version 0.9.8
|
||||
#### Version 0.9.8
|
||||
* Pebble: fix more reconnect issues
|
||||
* Pebble: fix deep sleep not being detected with Firmware 3.12 when using Pebble Health
|
||||
* Pebble: option in AppManager to delete files from cache
|
||||
@ -296,7 +340,7 @@
|
||||
* Pebble: fix music information being messed up
|
||||
* Honour "Do Not Disturb" for phone calls and SMS
|
||||
|
||||
####Version 0.9.7
|
||||
#### Version 0.9.7
|
||||
* Pebble: hopefully fix some reconnect issues
|
||||
* Mi Band: fix live activity monitoring running forever if back button pressed
|
||||
* Mi Band: allow low latency firmware updates, fixes update with some phones
|
||||
@ -304,14 +348,14 @@
|
||||
* Show aliases for BT Devices if they had been renamed in BT Settings
|
||||
* Do not show a hint about App Manager when a Mi Band is connected
|
||||
|
||||
####Version 0.9.6
|
||||
#### Version 0.9.6
|
||||
* Again some UI/theme improvements
|
||||
* New preference to reconnect after connection loss (defaults to true)
|
||||
* Fix crash when dealing with certain old preference values
|
||||
* Mi Band: automatically reconnect when back in range after connection loss
|
||||
* Mi Band 1S: display heart rate value again when invoked via the Debug view
|
||||
|
||||
####Version 0.9.5
|
||||
#### Version 0.9.5
|
||||
* Several UI Improvements
|
||||
* Easier First-time setup by using a FAB
|
||||
* Optional Dark Theme
|
||||
@ -321,7 +365,7 @@
|
||||
* Mi Band 1S: Initial live heartrate tracking
|
||||
* Fix certain crash in charts activity on slower devices (#277)
|
||||
|
||||
####Version 0.9.4
|
||||
#### Version 0.9.4
|
||||
* Pebble: support pebble health datalog messages of firmware 3.11 (this adds support for deep sleep!)
|
||||
* Pebble: try to reconnect on new notifications and phone calls when connection was lost unexpectedly
|
||||
* Pebble: delay between reconnection attempts (from 1 up to 64 seconds)
|
||||
@ -331,24 +375,24 @@
|
||||
* Mi Band 1S: full support for firmware upgrade/downgrade (both for Mi Band and heart rate sensor) (#234)
|
||||
* Mi Band 1S: fix device detection for certain versions
|
||||
|
||||
####Version 0.9.3
|
||||
#### Version 0.9.3
|
||||
* Pebble: Fix Pebble Health activation (was not available in the App Manager)
|
||||
* Simplify connection state display (only connecting->connected)
|
||||
* Small improvements to the pairing activity
|
||||
* Mi Band 1S: Fix for mi band firmware update
|
||||
|
||||
####Version 0.9.2
|
||||
#### Version 0.9.2
|
||||
* Mi Band: Fix update of second (HR) firmware on Mi1S (#234)
|
||||
* Fix ordering issue of device infos being displayed
|
||||
|
||||
####Version 0.9.1
|
||||
#### Version 0.9.1
|
||||
* Mi Band: fix sporadic connection problems (stuck on "Initializing" #249)
|
||||
* Mi Band: enable low latency connection (faster) during initialization and activity sync
|
||||
* Mi Band: better feedback for firmware update
|
||||
* Device Item is now clickable also when the information entries are visible
|
||||
* Fix enabling log file writing #261
|
||||
|
||||
####Version 0.9.0
|
||||
#### Version 0.9.0
|
||||
* Pebble: Support for configuring watchfaces/apps locally (clay) or though webbrowser (some do not work)
|
||||
* Pebble: hide the alarm management activity as it's unsupported
|
||||
* Mi Band: Improve firmware detection and updates, including 1S support
|
||||
@ -357,19 +401,19 @@
|
||||
* Do not display activity samples when navigating too far in the past
|
||||
* Fix auto connect which was broken under some circumstances
|
||||
|
||||
####Version 0.8.2
|
||||
#### Version 0.8.2
|
||||
* Fix database creation and updates (thanks @feclare)
|
||||
* Add experimental widget to set the alarm time to a configurable number of hours in the future (thanks @0nse)
|
||||
* Use ckChangeLog to display the Changelog within Gadgetbridge
|
||||
* Workaround to fix logfile rotation (bug in logback-android)
|
||||
|
||||
####Version 0.8.1
|
||||
#### Version 0.8.1
|
||||
* Pebble: install (and start) freshly-installed apps on the watch instead of showing a Toast that tells the user to do so. (only applies to firmware 3.x)
|
||||
* Pebble: fix crash while receiving Health data
|
||||
* Mi Band 1S: support for synchronizing activity data (#205)
|
||||
* Mi Band 1S: support for reading the heart rate via the "Debug Screen" #178
|
||||
|
||||
####Version 0.8.0
|
||||
#### Version 0.8.0
|
||||
* Pebble: Support Pebble Health: steps/activity data are stored correctly. Sleep time is considered as light sleep. Deep sleep is discarded. The pebble will send data where it seems appropriate, there is no action to perform on the watch for this to happen.
|
||||
* Pebble: Fix support for newer version of morpheuz (>=3.3?)
|
||||
* Pebble: Allow to select the preferred activity tracker via settings activity (Health, Misfit, Morpheuz)
|
||||
@ -379,17 +423,17 @@
|
||||
* Very basic support Android 6 runtime permission
|
||||
* Fix layout of the alarms activity
|
||||
|
||||
####Version 0.7.4
|
||||
#### Version 0.7.4
|
||||
* Refactored the settings activity: User details are now generic instead of miband specific. Old settings are preserved.
|
||||
* Pebble: Fix regression with broken active reconnect since 0.7.0
|
||||
* Pebble: Support activation and deactivation of Pebble Health. Activation uses the User details as seen above. Insights are NOT activated.
|
||||
Please be aware that deactivation does NOT delete the data stored on the watch (but it seems to stop the tracking), and we do not know how to switch to metric length units.
|
||||
|
||||
####Version 0.7.3
|
||||
#### Version 0.7.3
|
||||
* Pebble: Report connection state to PebbleKit companion apps via content provider. NOTE: Makes Gadgetbridge mutual exclusive with the original Pebble app.
|
||||
* Ignore generic notification when from SMSSecure when SMS Notifications are on
|
||||
|
||||
####Version 0.7.2
|
||||
#### Version 0.7.2
|
||||
* Pebble: Allow replying to generic notifications that contain a wearable reply action (tested with Signal)
|
||||
* Pebble: Support setting up a common suffix for canned replies (defaults to " (canned reply)")
|
||||
* Mi Band: Avoid NPEs when aborting an erroneous sync #205
|
||||
@ -397,11 +441,11 @@
|
||||
* Add a confirmation dialog when performing a db import
|
||||
* Sort blacklist by package names
|
||||
|
||||
####Version 0.7.1
|
||||
#### Version 0.7.1
|
||||
* Pebble: allow reinstallation of apps in pbw-cache from App Manager (long press menu)
|
||||
* Pebble: Fix regression which freezes Gadgetbridge when disconnecting via long-press menu
|
||||
|
||||
####Version 0.7.0
|
||||
#### Version 0.7.0
|
||||
* Read upcoming events (up to 7 days in the future). Requires READ_CALENDAR permission
|
||||
* Fix double SMS on Sony Android and Android 6.0
|
||||
* Pebble: Support replying to SMS form the watch (canned replies)
|
||||
@ -413,7 +457,7 @@
|
||||
* Mi Band: Display unique devices Names, not just "MI"
|
||||
* Some new and updated icons
|
||||
|
||||
####Version 0.6.9
|
||||
#### Version 0.6.9
|
||||
* Pebble: Store app details in pbw-cache and display them in app manager on firmware 3.x
|
||||
* Pebble: Increase maximum notification body length from 255 to 512 bytes on firmware 3.x
|
||||
* Pebble: Support installing .pbl (language files) on firmware 3.x
|
||||
@ -426,7 +470,7 @@
|
||||
* Mi Band: KitKat: hopefully fixed showing the progress bar during activity data synchronization (#155)
|
||||
* Mi Band 1S: hopefully fixed connection errors (#178) Notifications probably do not work yet, though
|
||||
|
||||
####Version 0.6.8
|
||||
#### Version 0.6.8
|
||||
* Mi Band: support for Firmware upgrade/downgrade on Mi Band 1A (white LEDs, no heartrate sensor)
|
||||
* Pebble: fix regression in 0.6.7 when installing pbw/pbz files from content providers (eg. download manager)
|
||||
* Pebble: fix installation of pbw files on firmware 3.x when using content providers (eg. download manager)
|
||||
@ -434,26 +478,26 @@
|
||||
+ Treat Signal notifications as chat notifications
|
||||
* Fix crash when contacts cannot be read on Android 6.0 (non-granted permissions)
|
||||
|
||||
####Version 0.6.7
|
||||
#### Version 0.6.7
|
||||
* Pebble: Allow installation of 3.x apps on OG Pebble (FW will be released soon)
|
||||
* Fix crashes on startup when logging is enabled or when entering the app manager on some phones
|
||||
+ Fix Pebble being detected as MI when unpaired and autoconnect is enabled
|
||||
* Fix Crash when not having K9 Mail permissions (happens when installing K9 after Gadgetbridge) (#175)
|
||||
|
||||
####Version 0.6.6
|
||||
#### Version 0.6.6
|
||||
* Mi Band: Huge performance improvement fetching activity data
|
||||
* Mi Band: attempt at fixing connection problems (#156)
|
||||
* Pebble: Try to interpret sleep data from Misfit data
|
||||
* Fix exporting the activity database on devices with read-only external storage (#153)
|
||||
* Fix totally wrong sleep time in the sleep chart
|
||||
|
||||
####Version 0.6.5
|
||||
#### Version 0.6.5
|
||||
* Mi Band: Support "Locate Device" with Mi Band 1A (and Mi Band 1 with new firmware)
|
||||
* Pebble: Support syncing steps from Misfit (untested features must be turned on to see them), intensity=steps, no sleep support yet
|
||||
* Disable activity fetching when not supported
|
||||
* Small improvements to live activity charts
|
||||
|
||||
####Version 0.6.4
|
||||
#### Version 0.6.4
|
||||
* Support pull down to synchronize activity data (#138)
|
||||
* Display tabs in the Charts activity (#139)
|
||||
* Mi Band: initial support for Mi Band 1a (the one with white LEDs) (thanks @sarg) (#136)
|
||||
@ -461,23 +505,23 @@
|
||||
* Register/unregister BroadcastReceivers instead of enabling/disabling them with PackageManager (#134)
|
||||
(should fix disconnection because the service is being killed)
|
||||
|
||||
####Version 0.6.3
|
||||
#### Version 0.6.3
|
||||
* Pebble: support installation of language files (.pbl) on FW 2.x
|
||||
* Try to prevent service being killed by disallowing backups
|
||||
|
||||
####Version 0.6.2
|
||||
#### Version 0.6.2
|
||||
* Mi Band: support firmware version 1.0.10.14 (and onwards?) vibration
|
||||
* Mi Band: get device name from official BT SIG endpoint
|
||||
* Mi Band: initial support for displaying live activity data, screen stays on
|
||||
|
||||
####Version 0.6.1
|
||||
#### Version 0.6.1
|
||||
* Pebble: Allow muting (blacklisting) Apps from within generic notifications on the watch
|
||||
* Pebble: Detect all known Pebble Versions including new "chalk" platform (Pebble Time Round)
|
||||
* Option to ignore phone calls (useful for Pebble Dialer)
|
||||
* Mi Band: Added progressbar for activity data transfer and fixes for firmware transfer progressbar
|
||||
* Bugfix for app blacklist (some checkboxes where wrongly drawn as checked)
|
||||
|
||||
####Version 0.6.0
|
||||
#### Version 0.6.0
|
||||
* Pebble: WIP implementation of PebbleKit Intents to make some 3rd party Android apps work with the Pebble (eg. Ventoo)
|
||||
* Pebble: Option to set reconnection attempts in settings (one attempt usually takes about 5 seconds)
|
||||
* Support controlling all audio players that react to media buttons (can be chosen in settings)
|
||||
@ -486,13 +530,13 @@
|
||||
* Allow opening firmware / app files from the download manager "app" (technically a content provider)
|
||||
* Mi Band: whitelisted a few firmware versions
|
||||
|
||||
####Version 0.5.4
|
||||
#### Version 0.5.4
|
||||
* Mi Band: allow the transfer of activity data without clearing MiBand's memory
|
||||
* Pebble: for generic notifications use generic icon instead of SMS icons on FW 3.x (thanks @roidelapluie)
|
||||
* Pebble: use different icons and background colors for specific groups of applications (chat, mail, etc) (thanks @roidelapluie)
|
||||
* In settings, support blacklisting apps for generic notifications
|
||||
|
||||
####Version 0.5.3
|
||||
#### Version 0.5.3
|
||||
* Pebble: For generic notifications, support dismissing individual notifications and "Open on Phone" feature (OG & PT)
|
||||
* Pebble: Allow to treat K9 notifications as generic notifications (if notification mode is set to never)
|
||||
* Ignore QKSMS notifications to avoid double notification for incoming SMS
|
||||
@ -500,7 +544,7 @@
|
||||
* Device state again visible on lockscreen
|
||||
* Date display and navigation now working properly for all charts
|
||||
|
||||
####Version 0.5.2
|
||||
#### Version 0.5.2
|
||||
* Pebble: support "dismiss all" action also on Pebble Time/FW 3.x notifications
|
||||
* Mi Band: show a notification when the battery is below 10%
|
||||
* Graphs are now using the same theme as the rest of the application
|
||||
@ -508,11 +552,11 @@
|
||||
* Remove unused settings option in charts view
|
||||
* Build target is now Android SDK 23 (Marshmallow)
|
||||
|
||||
####Version 0.5.1
|
||||
#### Version 0.5.1
|
||||
* Pebble: support taking screenshot from Pebble Time
|
||||
* Fix broken "find lost device" which was broken in 0.5.0
|
||||
|
||||
####Version 0.5.0
|
||||
#### Version 0.5.0
|
||||
* Mi Band: fix setting wear location
|
||||
* Pebble: experimental watchapp installation support for FW 3.x/Pebble Time
|
||||
* Pebble: support Pebble emulator via TCP connection (needs rebuild with INTERNET permission)
|
||||
@ -521,7 +565,7 @@
|
||||
* Support going forward/backwards in time in the activity charts
|
||||
* Various small bugfixes to the App/FW Installation Activity
|
||||
|
||||
####Version 0.4.6
|
||||
#### Version 0.4.6
|
||||
* Mi Band: Fixed negative number of steps displayed (#91)
|
||||
* Mi Band: fixed (re-) connection problems after band getting disconnected
|
||||
* Pebble: new option to enable untested code (enable only if you like bad surprises)
|
||||
@ -531,7 +575,7 @@
|
||||
* Small firmware installation improvements
|
||||
* Various refactorings and code cleanups
|
||||
|
||||
####Version 0.4.5
|
||||
#### Version 0.4.5
|
||||
* Enhancement to activity graphs: new graph showing the number of steps done today and in the last week
|
||||
* New preference to set the desired fitness goal (number of steps to walk in one day)
|
||||
* Mi Band: support for setting the fitness goal (the band will show the progress to the goal with the LEDs and vibrates when the goal is reached)
|
||||
@ -539,7 +583,7 @@
|
||||
* Mi Band: support for flashing firmware from .fw files (upgrades and downgrades are possible)
|
||||
* Fixed crash when synchronizing activity data in the graphs activity and changing device orientation
|
||||
|
||||
####Version 0.4.4
|
||||
#### Version 0.4.4
|
||||
* Set Gadgetbridge notification visibility to public, to show the connection status on the lockscreen
|
||||
* Support for backup up and restoring of the activity database (via Debug activity)
|
||||
* Support for graceful upgrades and downgrades, keeping your activity database intact
|
||||
@ -549,24 +593,24 @@
|
||||
* Pebble: make FW 3.x notifications available by default
|
||||
* Mi Band: Set the graphs activity as the default action available with a single tap on the connected device
|
||||
|
||||
####Version 0.4.3
|
||||
#### Version 0.4.3
|
||||
* Mi Band: Support for setting alarms
|
||||
* Mi Band: Bugfix for activity data synchronization
|
||||
|
||||
####Version 0.4.2
|
||||
#### Version 0.4.2
|
||||
* Material style for Lollipop
|
||||
* Support for finding a lost device (vibrate until cancelled)
|
||||
* Mi Band: Support for vibration profiles, configurable for notifications
|
||||
* Pebble: Support taking screenshots from the device context menu (Pebble Time not supported yet)
|
||||
|
||||
####Version 0.4.1
|
||||
#### Version 0.4.1
|
||||
* New icons, thanks xphnx!
|
||||
* Improvements to Sleep Monitor charts
|
||||
* Pebble: use new Sleep Monitor for Morpheuz (previously Mi Band only)
|
||||
* Pebble: experimental support for FW 3.x notification protocol
|
||||
* Pebble: dev option to force latest notification protocol
|
||||
|
||||
####Version 0.4.0
|
||||
#### Version 0.4.0
|
||||
* Pebble: Initial Morpheuz protocol support for getting sleep data
|
||||
* Pebble: Support launching of watchapps though the AppManager Activity
|
||||
* Pebble: Support CM 12.1 default music app (Eleven)
|
||||
@ -580,33 +624,33 @@
|
||||
* Fix Debug activity (SMS and E-Mail buttons were broken)
|
||||
* Add Turkish translation contributed by Tarik Sekmen
|
||||
|
||||
####Version 0.3.5
|
||||
#### Version 0.3.5
|
||||
* Add discovery and pairing Activity for Pebble and Mi Band
|
||||
* Listen for Pebble Message Intents and forward notifications (used by Conversations)
|
||||
* Make strings translatable and add German, Italian, Russian, Spanish and Korean translations
|
||||
* Mi Band: Display battery status
|
||||
|
||||
####Version 0.3.4
|
||||
#### Version 0.3.4
|
||||
* Pebble: Huge speedup for app/firmware installation.
|
||||
* Pebble: Use a separate notification with progress bar for installation procedure
|
||||
* Pebble: Bugfix for being stuck while waiting for a slot, when none is available
|
||||
* Mi Band: Display connection status in notification (previously Pebble only)
|
||||
|
||||
####Version 0.3.3
|
||||
#### Version 0.3.3
|
||||
* Pebble: Try to reduce battery usage by acknowledging datalog packets
|
||||
* Mi Band: Set current time on the device (thanks to PR by @danielegobbetti)
|
||||
* More robust connection state handling and display
|
||||
|
||||
####Version 0.3.2
|
||||
#### Version 0.3.2
|
||||
* Mi Band: Fix for notifications only working after manual connection
|
||||
* Mi Band: Display firmware version
|
||||
* Pebble: Display hardware revision
|
||||
* Pebble: Check if firmware is compatible before allowing installation
|
||||
|
||||
####Version 0.3.1
|
||||
#### Version 0.3.1
|
||||
* Mi Band: Fix for notifications only working in Debug
|
||||
|
||||
####Version 0.3.0
|
||||
#### Version 0.3.0
|
||||
* Mi Band: Initial support (see README.md)
|
||||
* Pebble: Firmware installation (USE AT YOUR OWN RISK)
|
||||
* Pebble: Fix installation problems with certain .pbw files
|
||||
@ -614,7 +658,7 @@
|
||||
* Add icon for activity tracker apps (icon by xphnx)
|
||||
* Let the application quit when in reconnecting state
|
||||
|
||||
####Version 0.2.0
|
||||
#### Version 0.2.0
|
||||
* Experimental pbw installation support (watchfaces/apps)
|
||||
* New icons for device and app lists
|
||||
* Fix for device list not refreshing when Bluetooth gets turned on
|
||||
@ -622,31 +666,31 @@
|
||||
* Fix for crash on some devices when creating a debug notification
|
||||
* Lots of internal changes preparing multi device support
|
||||
|
||||
####Version 0.1.5
|
||||
#### Version 0.1.5
|
||||
* Fix for DST (summer time)
|
||||
* Option to sync time on connect (enabled by default)
|
||||
* Opening .pbw files with Gadgetbridge prints some package information
|
||||
(This was not meant to be released yet, but the DST fix made a new release necessary)
|
||||
|
||||
####Version 0.1.4
|
||||
#### Version 0.1.4
|
||||
* New AppManager shows installed Apps/Watchfaces (removal possible via context menu)
|
||||
* Allow back navigation in ActionBar (Debug and AppMananger Activities)
|
||||
* Make sure Intent broadcasts do not leave Gadgetbridge
|
||||
* Show hint in the Main Activity (tap to connect etc)
|
||||
|
||||
####Version 0.1.3
|
||||
#### Version 0.1.3
|
||||
* Remove the connect button, list all supported devices and connect on tap instead
|
||||
* Display connection status and firmware of connected devices in the device list
|
||||
* Remove quit button from the service notification, put a quit item in the context menu instead
|
||||
|
||||
####Version 0.1.2
|
||||
#### Version 0.1.2
|
||||
* Added option to start Gadgetbridge and connect automatically when Bluetooth is turned on
|
||||
* stop service if Bluetooth is turned off
|
||||
* try to reconnect if connection was lost
|
||||
|
||||
####Version 0.1.1
|
||||
#### Version 0.1.1
|
||||
* Fixed various bugs regarding K-9 Mail notifications.
|
||||
* "Generic notification support" in Setting now opens Androids "Notification access" dialog.
|
||||
|
||||
####Version 0.1.0
|
||||
#### Version 0.1.0
|
||||
* Initial release
|
||||
|
@ -1,21 +1,27 @@
|
||||
The following artwork is licensed under the following licenses
|
||||
|
||||
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0):
|
||||
Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0):
|
||||
ic_launcher.png
|
||||
ic_device_pebble.png
|
||||
ic_device_miband.png
|
||||
ic_device_lovetoy.png
|
||||
ic_device_hplus.png
|
||||
ic_device_default.png
|
||||
ic_activitytracker.png
|
||||
ic_watchface.png
|
||||
ic_languagepack.png
|
||||
ic_firmware.png
|
||||
ic_watchapp.png
|
||||
ic_systemapp.png
|
||||
icon.png (fastlane metadata directories)
|
||||
featureGraphic.png (fastlane metadata directories)
|
||||
(All of the above by xphnx)
|
||||
|
||||
Creative Commons Attribution-NonCommercial-ShareAlike (CC BY-NC-SA):
|
||||
ic_launcher.png (by Joseph Kim)
|
||||
|
||||
Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0):
|
||||
"GET IT ON F-Droid" button by Laura Kalbag. Source: https://ind.ie/about/blog/f-droid-button/
|
||||
|
||||
Creative Commons Attribution 3.0 Unported license (CC BY-3.0):
|
||||
ic_notification_battery_low.png by Picol.org. Source: https://commons.wikimedia.org/wiki/File:Battery_1_Picol_icon.svg
|
||||
|
||||
Creative Commons Attribution 3.0 United States (CC BY-3.0 US):
|
||||
ic_donate by Peter van Driel https://thenounproject.com/term/donate/239009/
|
||||
|
18
README.md
@ -2,8 +2,13 @@ Gadgetbridge
|
||||
============
|
||||
|
||||
Gadgetbridge is an Android (4.4+) application which will allow you to use your
|
||||
Pebble or Mi Band without the vendor's closed source application and without the
|
||||
need to create an account and transmit any of your data to the vendor's servers.
|
||||
Pebble, Mi Band, Amazfit Bit and HPlus device (and more) without the vendor's closed source application
|
||||
and without the need to create an account and transmit any of your data to the
|
||||
vendor's servers.
|
||||
|
||||
[Homepage](https://gadgetbridge.org)
|
||||
|
||||
[Blog](https://blog.gadgetbridge.org)
|
||||
|
||||
[![Build](https://travis-ci.org/Freeyourgadget/Gadgetbridge.svg?branch=master)](https://travis-ci.org/Freeyourgadget/Gadgetbridge)
|
||||
|
||||
@ -11,16 +16,17 @@ need to create an account and transmit any of your data to the vendor's servers.
|
||||
|
||||
[![Gadgetbridge on F-Droid](/Get_it_on_F-Droid.svg.png?raw=true "Download from F-Droid")](https://f-droid.org/repository/browse/?fdid=nodomain.freeyourgadget.gadgetbridge)
|
||||
|
||||
[List of changes](CHANGELOG.md)
|
||||
[List of changes](https://github.com/Freeyourgadget/Gadgetbridge/blob/master/CHANGELOG.md)
|
||||
|
||||
## Supported Devices
|
||||
* Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, Pebble Time Round [Wiki section about this device](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble)
|
||||
* Pebble 2 (add the device from within Gadgetbridge!) [Wiki section about pebble](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble), most parts apply to Pebble 2 as well
|
||||
* Mi Band, Mi Band 1A, Mi Band 1S [Wiki section about this device](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band)
|
||||
* Mi Band 2 [Wiki section about mi band](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band), some parts apply to mi band 2 as well
|
||||
* Vibratissimo (experimental)
|
||||
* Liveview
|
||||
* Amazfit Bip (WIP) [Wiki section about the Amazfit Bip](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip)
|
||||
* HPlus Devices (e.g. ZeBand) [Wiki section about this device](https://github.com/Freeyourgadget/Gadgetbridge/wiki/HPlus)
|
||||
* Liveview
|
||||
* Vibratissimo (experimental)
|
||||
|
||||
## Features (Pebble)
|
||||
|
||||
@ -141,7 +147,7 @@ Contributions are welcome, be it feedback, bugreports, documentation, translatio
|
||||
on any of the open [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues?q=is%3Aopen+is%3Aissue);
|
||||
just leave a comment that you're working on one to avoid duplicated work.
|
||||
|
||||
Translations can be contributed via https://www.transifex.com/projects/p/gadgetbridge/resource/strings/ or manually.
|
||||
Translations can be contributed via https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/
|
||||
|
||||
## Do you have further questions or feedback?
|
||||
|
||||
|
@ -26,8 +26,8 @@ android {
|
||||
targetSdkVersion 25
|
||||
|
||||
// note: always bump BOTH versionCode and versionName!
|
||||
versionName "0.19.1"
|
||||
versionCode 94
|
||||
versionName "0.20.2"
|
||||
versionCode 100
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
buildTypes {
|
||||
|
@ -63,6 +63,10 @@
|
||||
android:name=".activities.AppBlacklistActivity"
|
||||
android:label="@string/title_activity_appblacklist"
|
||||
android:parentActivityName=".activities.SettingsActivity" />
|
||||
<activity
|
||||
android:name=".activities.CalBlacklistActivity"
|
||||
android:label="@string/title_activity_calblacklist"
|
||||
android:parentActivityName=".activities.SettingsActivity" />
|
||||
<activity
|
||||
android:name=".activities.FwAppInstallerActivity"
|
||||
android:theme="@style/GadgetbridgeTheme.NoActionBar"
|
||||
@ -110,6 +114,26 @@
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
|
||||
<data android:pathPattern="/.*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\.pbw" />
|
||||
<data android:pathPattern="/.*\\..*\\.pbw" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.pbw" />
|
||||
@ -183,6 +207,26 @@
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
|
||||
<data android:pathPattern="/.*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\.pbw" />
|
||||
<data android:pathPattern="/.*\\..*\\.pbw" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.pbw" />
|
||||
|
@ -9,6 +9,18 @@
|
||||
<script type="text/javascript">
|
||||
</script>
|
||||
<style>
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
iframe {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
body {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
|
@ -75,6 +75,13 @@ function showStep(desiredStep) {
|
||||
}
|
||||
}
|
||||
|
||||
function hideSteps() {
|
||||
var steps = document.getElementsByClassName("step");
|
||||
for (var i = 0; i < steps.length; i ++) {
|
||||
steps[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function gbPebble() {
|
||||
this.configurationURL = null;
|
||||
this.configurationValues = null;
|
||||
@ -142,7 +149,18 @@ function gbPebble() {
|
||||
self.configurationURL = new Uri(url).addQueryParam("return_to", "gadgetbridge://"+UUID+"?config=true&json=");
|
||||
} else {
|
||||
//TODO: add custom return_to
|
||||
location.href = url;
|
||||
var iframe = document.getElementsByTagName('iframe')[0];
|
||||
var oldbody = document.getElementsByTagName("body")[0];
|
||||
if (iframe === undefined && oldbody !== undefined) {
|
||||
iframe = document.createElement("iframe");
|
||||
oldbody.parentNode.replaceChild(iframe,oldbody);
|
||||
} else {
|
||||
hideSteps();
|
||||
document.documentElement.appendChild(iframe);
|
||||
}
|
||||
|
||||
iframe.src = url;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -155,11 +173,15 @@ function gbPebble() {
|
||||
try {
|
||||
self.configurationValues = JSON.stringify(dict);
|
||||
document.getElementById("jsondata").innerHTML=self.configurationValues;
|
||||
return callbackAck;
|
||||
if (callbackAck != undefined) {
|
||||
callbackAck();
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
GBjs.gbLog("sendAppMessage failed");
|
||||
return callbackNack;
|
||||
if (callbackNack != undefined) {
|
||||
callbackNack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 28 KiB |
@ -24,6 +24,7 @@ import android.app.NotificationManager.Policy;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
@ -40,6 +41,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
@ -88,6 +90,7 @@ public class GBApplication extends Application {
|
||||
|
||||
public static final String ACTION_QUIT
|
||||
= "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit";
|
||||
public static final String ACTION_LANGUAGE_CHANGE = "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.language_change";
|
||||
|
||||
private static GBApplication app;
|
||||
|
||||
@ -102,6 +105,7 @@ public class GBApplication extends Application {
|
||||
}
|
||||
}
|
||||
};
|
||||
private static Locale language;
|
||||
|
||||
private DeviceManager deviceManager;
|
||||
|
||||
@ -157,9 +161,12 @@ public class GBApplication extends Application {
|
||||
setupExceptionHandler();
|
||||
|
||||
deviceManager = new DeviceManager(this);
|
||||
String language = prefs.getString("language", "default");
|
||||
setLanguage(language);
|
||||
|
||||
deviceService = createDeviceService();
|
||||
loadBlackList();
|
||||
loadAppsBlackList();
|
||||
loadCalendarsBlackList();
|
||||
|
||||
if (isRunningMarshmallowOrLater()) {
|
||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
@ -335,47 +342,107 @@ public class GBApplication extends Application {
|
||||
return NotificationManager.INTERRUPTION_FILTER_ALL;
|
||||
}
|
||||
|
||||
private static HashSet<String> blacklist = null;
|
||||
private static HashSet<String> apps_blacklist = null;
|
||||
|
||||
public static boolean isBlacklisted(String packageName) {
|
||||
return blacklist != null && blacklist.contains(packageName);
|
||||
public static boolean appIsBlacklisted(String packageName) {
|
||||
if (apps_blacklist == null) {
|
||||
GB.log("appIsBlacklisted: apps_blacklist is null!", GB.INFO, null);
|
||||
}
|
||||
return apps_blacklist != null && apps_blacklist.contains(packageName);
|
||||
}
|
||||
|
||||
public static void setBlackList(Set<String> packageNames) {
|
||||
public static void setAppsBlackList(Set<String> packageNames) {
|
||||
if (packageNames == null) {
|
||||
blacklist = new HashSet<>();
|
||||
GB.log("Set null apps_blacklist", GB.INFO, null);
|
||||
apps_blacklist = new HashSet<>();
|
||||
} else {
|
||||
blacklist = new HashSet<>(packageNames);
|
||||
apps_blacklist = new HashSet<>(packageNames);
|
||||
}
|
||||
saveBlackList();
|
||||
GB.log("New apps_blacklist has " + apps_blacklist.size() + " entries", GB.INFO, null);
|
||||
saveAppsBlackList();
|
||||
}
|
||||
|
||||
private static void loadBlackList() {
|
||||
blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.PACKAGE_BLACKLIST, null);
|
||||
if (blacklist == null) {
|
||||
blacklist = new HashSet<>();
|
||||
private static void loadAppsBlackList() {
|
||||
GB.log("Loading apps_blacklist", GB.INFO, null);
|
||||
apps_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.PACKAGE_BLACKLIST, null);
|
||||
if (apps_blacklist == null) {
|
||||
apps_blacklist = new HashSet<>();
|
||||
}
|
||||
GB.log("Loaded apps_blacklist has " + apps_blacklist.size() + " entries", GB.INFO, null);
|
||||
}
|
||||
|
||||
private static void saveBlackList() {
|
||||
private static void saveAppsBlackList() {
|
||||
GB.log("Saving apps_blacklist with " + apps_blacklist.size() + " entries", GB.INFO, null);
|
||||
SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
if (blacklist.isEmpty()) {
|
||||
if (apps_blacklist.isEmpty()) {
|
||||
editor.putStringSet(GBPrefs.PACKAGE_BLACKLIST, null);
|
||||
} else {
|
||||
editor.putStringSet(GBPrefs.PACKAGE_BLACKLIST, blacklist);
|
||||
Prefs.putStringSet(editor, GBPrefs.PACKAGE_BLACKLIST, apps_blacklist);
|
||||
}
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static void addToBlacklist(String packageName) {
|
||||
if (blacklist.add(packageName)) {
|
||||
saveBlackList();
|
||||
public static void addAppToBlacklist(String packageName) {
|
||||
if (apps_blacklist.add(packageName)) {
|
||||
saveAppsBlackList();
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void removeFromBlacklist(String packageName) {
|
||||
blacklist.remove(packageName);
|
||||
saveBlackList();
|
||||
public static synchronized void removeFromAppsBlacklist(String packageName) {
|
||||
GB.log("Removing from apps_blacklist: " + packageName, GB.INFO, null);
|
||||
apps_blacklist.remove(packageName);
|
||||
saveAppsBlackList();
|
||||
}
|
||||
|
||||
private static HashSet<String> calendars_blacklist = null;
|
||||
|
||||
public static boolean calendarIsBlacklisted(String calendarDisplayName) {
|
||||
if (calendars_blacklist == null) {
|
||||
GB.log("calendarIsBlacklisted: calendars_blacklist is null!", GB.INFO, null);
|
||||
}
|
||||
return calendars_blacklist != null && calendars_blacklist.contains(calendarDisplayName);
|
||||
}
|
||||
|
||||
public static void setCalendarsBlackList(Set<String> calendarNames) {
|
||||
if (calendarNames == null) {
|
||||
GB.log("Set null apps_blacklist", GB.INFO, null);
|
||||
calendars_blacklist = new HashSet<>();
|
||||
} else {
|
||||
calendars_blacklist = new HashSet<>(calendarNames);
|
||||
}
|
||||
GB.log("New calendars_blacklist has " + calendars_blacklist.size() + " entries", GB.INFO, null);
|
||||
saveCalendarsBlackList();
|
||||
}
|
||||
|
||||
public static void addCalendarToBlacklist(String calendarDisplayName) {
|
||||
if (calendars_blacklist.add(calendarDisplayName)) {
|
||||
saveCalendarsBlackList();
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeFromCalendarBlacklist(String calendarDisplayName) {
|
||||
calendars_blacklist.remove(calendarDisplayName);
|
||||
saveCalendarsBlackList();
|
||||
}
|
||||
|
||||
private static void loadCalendarsBlackList() {
|
||||
GB.log("Loading calendars_blacklist", GB.INFO, null);
|
||||
calendars_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.CALENDAR_BLACKLIST, null);
|
||||
if (calendars_blacklist == null) {
|
||||
calendars_blacklist = new HashSet<>();
|
||||
}
|
||||
GB.log("Loaded calendars_blacklist has " + calendars_blacklist.size() + " entries", GB.INFO, null);
|
||||
}
|
||||
|
||||
private static void saveCalendarsBlackList() {
|
||||
GB.log("Saving calendars_blacklist with " + calendars_blacklist.size() + " entries", GB.INFO, null);
|
||||
SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
if (calendars_blacklist.isEmpty()) {
|
||||
editor.putStringSet(GBPrefs.CALENDAR_BLACKLIST, null);
|
||||
} else {
|
||||
Prefs.putStringSet(editor, GBPrefs.CALENDAR_BLACKLIST, calendars_blacklist);
|
||||
}
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -459,6 +526,23 @@ public class GBApplication extends Application {
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static void setLanguage(String lang) {
|
||||
if (lang.equals("default")) {
|
||||
language = Resources.getSystem().getConfiguration().locale;
|
||||
} else {
|
||||
language = new Locale(lang);
|
||||
}
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(language);
|
||||
|
||||
// FIXME: I have no idea what I am doing
|
||||
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(ACTION_LANGUAGE_CHANGE);
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
|
||||
}
|
||||
|
||||
public static LimitedQueue getIDSenderLookup() {
|
||||
return mIDSenderLookup;
|
||||
}
|
||||
@ -496,4 +580,8 @@ public class GBApplication extends Application {
|
||||
public static GBApplication app() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public static Locale getLanguage() {
|
||||
return language;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -17,20 +17,28 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.InputType;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
|
||||
/**
|
||||
* A settings activity with support for preferences directly displaying their value.
|
||||
@ -42,6 +50,22 @@ public abstract class AbstractSettingsActivity extends AppCompatPreferenceActivi
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractSettingsActivity.class);
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case GBApplication.ACTION_LANGUAGE_CHANGE:
|
||||
setLanguage(GBApplication.getLanguage());
|
||||
break;
|
||||
case GBApplication.ACTION_QUIT:
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A preference value change listener that updates the preference's summary
|
||||
* to reflect its new value.
|
||||
@ -110,6 +134,12 @@ public abstract class AbstractSettingsActivity extends AppCompatPreferenceActivi
|
||||
} else {
|
||||
setTheme(R.style.GadgetbridgeTheme);
|
||||
}
|
||||
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(GBApplication.ACTION_LANGUAGE_CHANGE);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@ -127,6 +157,12 @@ public abstract class AbstractSettingsActivity extends AppCompatPreferenceActivi
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses should reimplement this to return the keys of those
|
||||
* preferences which should print its values as a summary below the
|
||||
@ -178,4 +214,8 @@ public abstract class AbstractSettingsActivity extends AppCompatPreferenceActivi
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void setLanguage(Locale language) {
|
||||
AndroidUtils.setLanguage(this, language);
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,8 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.SearchView;
|
||||
@ -32,7 +27,6 @@ import android.view.MenuItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.AppBlacklistAdapter;
|
||||
|
||||
@ -42,16 +36,6 @@ public class AppBlacklistActivity extends GBActivity {
|
||||
|
||||
private AppBlacklistAdapter appBlacklistAdapter;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(GBApplication.ACTION_QUIT)) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -78,10 +62,6 @@ public class AppBlacklistActivity extends GBActivity {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -93,10 +73,4 @@ public class AppBlacklistActivity extends GBActivity {
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,152 @@
|
||||
/* Copyright (C) 2017 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.activities;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Paint;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.CalendarContract;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
|
||||
public class CalBlacklistActivity extends GBActivity {
|
||||
|
||||
private final String[] EVENT_PROJECTION = new String[]{
|
||||
CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,
|
||||
CalendarContract.Calendars.CALENDAR_COLOR
|
||||
};
|
||||
private ArrayList<Calendar> calendarsArrayList;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_calblacklist);
|
||||
ListView calListView = (ListView) findViewById(R.id.calListView);
|
||||
|
||||
final Uri uri = CalendarContract.Calendars.CONTENT_URI;
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
|
||||
GB.toast(this, "Calendar permission not granted. Nothing to do.", Toast.LENGTH_SHORT, GB.WARN);
|
||||
return;
|
||||
}
|
||||
try (Cursor cur = getContentResolver().query(uri, EVENT_PROJECTION, null, null, null)) {
|
||||
calendarsArrayList = new ArrayList<>();
|
||||
while (cur != null && cur.moveToNext()) {
|
||||
calendarsArrayList.add(new Calendar(cur.getString(0), cur.getInt(1)));
|
||||
}
|
||||
}
|
||||
|
||||
ArrayAdapter<Calendar> calAdapter = new CalendarListAdapter(this, calendarsArrayList);
|
||||
calListView.setAdapter(calAdapter);
|
||||
calListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int i, long id) {
|
||||
Calendar item = calendarsArrayList.get(i);
|
||||
CheckBox selected = (CheckBox) view.findViewById(R.id.item_checkbox);
|
||||
toggleEntry(view);
|
||||
if (selected.isChecked()) {
|
||||
GBApplication.addCalendarToBlacklist(item.displayName);
|
||||
} else {
|
||||
GBApplication.removeFromCalendarBlacklist(item.displayName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void toggleEntry(View view) {
|
||||
TextView name = (TextView) view.findViewById(R.id.calendar_name);
|
||||
CheckBox checked = (CheckBox) view.findViewById(R.id.item_checkbox);
|
||||
|
||||
name.setPaintFlags(name.getPaintFlags() ^ Paint.STRIKE_THRU_TEXT_FLAG);
|
||||
checked.toggle();
|
||||
}
|
||||
|
||||
class Calendar {
|
||||
private final String displayName;
|
||||
private final int color;
|
||||
|
||||
public Calendar(String displayName, int color) {
|
||||
this.displayName = displayName;
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
private class CalendarListAdapter extends ArrayAdapter<Calendar> {
|
||||
|
||||
CalendarListAdapter(@NonNull Context context, @NonNull List<Calendar> calendars) {
|
||||
super(context, 0, calendars);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) {
|
||||
Calendar item = getItem(position);
|
||||
|
||||
if (view == null) {
|
||||
LayoutInflater inflater = (LayoutInflater) super.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = inflater.inflate(R.layout.item_cal_blacklist, parent, false);
|
||||
}
|
||||
|
||||
View color = view.findViewById(R.id.calendar_color);
|
||||
TextView name = (TextView) view.findViewById(R.id.calendar_name);
|
||||
CheckBox checked = (CheckBox) view.findViewById(R.id.item_checkbox);
|
||||
|
||||
if (GBApplication.calendarIsBlacklisted(item.displayName) && !checked.isChecked()) {
|
||||
toggleEntry(view);
|
||||
}
|
||||
color.setBackgroundColor(item.color);
|
||||
name.setText(item.displayName);
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Canvas;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
@ -49,6 +50,7 @@ import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.cketti.library.changelog.ChangeLog;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -56,6 +58,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAdapterv2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
@ -80,6 +83,9 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case GBApplication.ACTION_LANGUAGE_CHANGE:
|
||||
setLanguage(GBApplication.getLanguage());
|
||||
break;
|
||||
case GBApplication.ACTION_QUIT:
|
||||
finish();
|
||||
break;
|
||||
@ -170,6 +176,7 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
registerForContextMenu(deviceListView);
|
||||
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(GBApplication.ACTION_LANGUAGE_CHANGE);
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(DeviceManager.ACTION_DEVICES_CHANGED);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
@ -189,7 +196,7 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
checkAndRequestPermissions();
|
||||
}
|
||||
|
||||
ChangeLog cl = new ChangeLog(this);
|
||||
ChangeLog cl = createChangeLog();
|
||||
if (cl.isFirstRun()) {
|
||||
cl.getLogDialog().show();
|
||||
}
|
||||
@ -203,6 +210,13 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
unregisterForContextMenu(deviceListView);
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||
@ -235,8 +249,13 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
case R.id.action_quit:
|
||||
GBApplication.quit();
|
||||
return true;
|
||||
case R.id.donation_link:
|
||||
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("https://liberapay.com/Gadgetbridge")); //TODO: centralize if ever used somewhere else
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(i);
|
||||
return true;
|
||||
case R.id.external_changelog:
|
||||
ChangeLog cl = new ChangeLog(this);
|
||||
ChangeLog cl = createChangeLog();
|
||||
cl.getFullLogDialog().show();
|
||||
return true;
|
||||
}
|
||||
@ -244,6 +263,14 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
return true;
|
||||
}
|
||||
|
||||
private ChangeLog createChangeLog() {
|
||||
String css = ChangeLog.DEFAULT_CSS;
|
||||
css += "body { "
|
||||
+ "color: " + AndroidUtils.getTextColorHex(getBaseContext()) + "; "
|
||||
+ "background-color: " + AndroidUtils.getBackgroundColorHex(getBaseContext()) + ";" +
|
||||
"}";
|
||||
return new ChangeLog(this, css);
|
||||
}
|
||||
private void launchDiscoveryActivity() {
|
||||
startActivity(new Intent(this, DiscoveryActivity.class));
|
||||
}
|
||||
@ -288,4 +315,7 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[wantedPermissions.size()]), 0);
|
||||
}
|
||||
|
||||
private void setLanguage(Locale language) {
|
||||
AndroidUtils.setLanguage(this, language);
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Bundle;
|
||||
@ -62,9 +61,6 @@ public class DbManagementActivity extends GBActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_db_management);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
|
||||
dbPath = (TextView) findViewById(R.id.activity_db_management_path);
|
||||
dbPath.setText(getExternalPath());
|
||||
|
||||
|
@ -78,10 +78,6 @@ public class DebugActivity extends GBActivity {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case GBApplication.ACTION_QUIT: {
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
case ACTION_REPLY: {
|
||||
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||
CharSequence reply = remoteInput.getCharSequence(EXTRA_REPLY);
|
||||
@ -104,7 +100,6 @@ public class DebugActivity extends GBActivity {
|
||||
setContentView(R.layout.activity_debug);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
filter.addAction(ACTION_REPLY);
|
||||
filter.addAction(DeviceService.ACTION_HEARTRATE_MEASUREMENT);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
|
@ -221,9 +221,6 @@ public class ExternalPebbleJSActivity extends GBActivity {
|
||||
|
||||
if (passKey) {
|
||||
Object obj = in.get(inKey);
|
||||
if (obj instanceof Boolean) {
|
||||
obj = ((Boolean) obj) ? "true" : "false";
|
||||
}
|
||||
out.put(outKey, obj);
|
||||
} else {
|
||||
GB.toast("Discarded key " + inKey + ", not found in the local configuration and is not an integer key.", Toast.LENGTH_SHORT, GB.WARN);
|
||||
|
@ -97,9 +97,7 @@ public class FwAppInstallerActivity extends AppCompatActivity implements Install
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (GBApplication.ACTION_QUIT.equals(action)) {
|
||||
finish();
|
||||
} else if (GBDevice.ACTION_DEVICE_CHANGED.equals(action)) {
|
||||
if (GBDevice.ACTION_DEVICE_CHANGED.equals(action)) {
|
||||
device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
if (device != null) {
|
||||
refreshBusyState(device);
|
||||
@ -181,7 +179,6 @@ public class FwAppInstallerActivity extends AppCompatActivity implements Install
|
||||
detailsListView.setAdapter(mDetailsItemAdapter);
|
||||
setInstallEnabled(false);
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
filter.addAction(GB.ACTION_DISPLAY_MESSAGE);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
|
@ -17,44 +17,64 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.os.LocaleList;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
|
||||
public class GBActivity extends AppCompatActivity {
|
||||
private void setLanguage(String language) {
|
||||
Locale locale;
|
||||
if (language.equals("default")) {
|
||||
locale = Locale.getDefault();
|
||||
} else {
|
||||
locale = new Locale(language);
|
||||
}
|
||||
Configuration config = new Configuration();
|
||||
config.locale = locale;
|
||||
|
||||
// FIXME: I have no idea what I am doing
|
||||
getApplicationContext().getResources().updateConfiguration(config, getApplicationContext().getResources().getDisplayMetrics());
|
||||
getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case GBApplication.ACTION_LANGUAGE_CHANGE:
|
||||
setLanguage(GBApplication.getLanguage());
|
||||
break;
|
||||
case GBApplication.ACTION_QUIT:
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void setLanguage(Locale language) {
|
||||
AndroidUtils.setLanguage(this, language);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(GBApplication.ACTION_LANGUAGE_CHANGE);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
if (GBApplication.isDarkThemeEnabled()) {
|
||||
setTheme(R.style.GadgetbridgeThemeDark);
|
||||
} else {
|
||||
setTheme(R.style.GadgetbridgeTheme);
|
||||
}
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
String language = prefs.getString("language", "default");
|
||||
setLanguage(language);
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
@ -97,6 +97,15 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
}
|
||||
});
|
||||
|
||||
pref = findPreference("pref_key_blacklist_calendars");
|
||||
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent enableIntent = new Intent(SettingsActivity.this, CalBlacklistActivity.class);
|
||||
startActivity(enableIntent);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
pref = findPreference("pebble_emu_addr");
|
||||
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
@ -143,6 +152,25 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
|
||||
});
|
||||
|
||||
pref = findPreference("language");
|
||||
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
String newLang = newVal.toString();
|
||||
try {
|
||||
GBApplication.setLanguage(newLang);
|
||||
} catch (Exception ex) {
|
||||
GB.toast(getApplicationContext(),
|
||||
"Error setting language: " + ex.getLocalizedMessage(),
|
||||
Toast.LENGTH_LONG,
|
||||
GB.ERROR,
|
||||
ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (!GBApplication.isRunningMarshmallowOrLater()) {
|
||||
pref = findPreference("notification_filter");
|
||||
PreferenceCategory category = (PreferenceCategory) findPreference("pref_key_notifications");
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -16,12 +16,7 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -33,17 +28,6 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class VibrationActivity extends GBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VibrationActivity.class);
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case GBApplication.ACTION_QUIT: {
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
private SeekBar seekBar;
|
||||
|
||||
@Override
|
||||
@ -51,11 +35,6 @@ public class VibrationActivity extends GBActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_vibration);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
registerReceiver(mReceiver, filter);
|
||||
|
||||
seekBar = (SeekBar) findViewById(R.id.vibration_seekbar);
|
||||
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
@ -77,11 +56,4 @@ public class VibrationActivity extends GBActivity {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
unregisterReceiver(mReceiver);
|
||||
}
|
||||
}
|
||||
|
@ -18,17 +18,13 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@ -45,7 +41,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractFragmentPagerAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragmentActivity;
|
||||
@ -61,18 +56,6 @@ public class AppManagerActivity extends AbstractGBFragmentActivity {
|
||||
|
||||
private GBDevice mGBDevice = null;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case GBApplication.ACTION_QUIT:
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public GBDevice getGBDevice() {
|
||||
return mGBDevice;
|
||||
}
|
||||
@ -104,11 +87,6 @@ public class AppManagerActivity extends AbstractGBFragmentActivity {
|
||||
}
|
||||
});
|
||||
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
// Set up the ViewPager with the sections adapter.
|
||||
ViewPager viewPager = (ViewPager) findViewById(R.id.appmanager_pager);
|
||||
if (viewPager != null) {
|
||||
@ -232,10 +210,4 @@ public class AppManagerActivity extends AbstractGBFragmentActivity {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
@ -487,7 +487,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
}
|
||||
activityEntries.add(createBarEntry(value, ts));
|
||||
if (hr && isValidHeartRateValue(sample.getHeartRate())) {
|
||||
if (lastHrSampleIndex > -1 && ts - lastHrSampleIndex > 60*HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
|
||||
if (lastHrSampleIndex > -1 && ts - lastHrSampleIndex > 1800*HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
|
||||
heartrateEntries.add(createLineEntry(0, lastHrSampleIndex + 1));
|
||||
heartrateEntries.add(createLineEntry(0, ts - 1));
|
||||
}
|
||||
@ -530,7 +530,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
List<IBarDataSet> list = new ArrayList<>();
|
||||
list.add(activitySet);
|
||||
BarData barData = new BarData(list);
|
||||
barData.setBarWidth(100f);
|
||||
barData.setBarWidth(200f);
|
||||
// barData.setGroupSpace(0);
|
||||
combinedData.setData(barData);
|
||||
|
||||
@ -595,10 +595,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
|
||||
protected LineDataSet createHeartrateSet(List<Entry> values, String label) {
|
||||
LineDataSet set1 = new LineDataSet(values, label);
|
||||
set1.setLineWidth(0.8f);
|
||||
set1.setLineWidth(2.2f);
|
||||
set1.setColor(HEARTRATE_COLOR);
|
||||
// set1.setDrawCubic(true);
|
||||
set1.setMode(LineDataSet.Mode.CUBIC_BEZIER);
|
||||
set1.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
|
||||
set1.setCubicIntensity(0.1f);
|
||||
set1.setDrawCircles(false);
|
||||
// set1.setCircleRadius(2f);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Gobbetti, Vebryn
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -17,7 +17,12 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
|
||||
@ -25,6 +30,13 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
class ActivityAnalysis {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ActivityAnalysis.class);
|
||||
|
||||
// store raw steps and duration
|
||||
protected HashMap<Integer, Long> stats = new HashMap<Integer, Long>();
|
||||
// max speed determined from samples
|
||||
private int maxSpeed = 0;
|
||||
|
||||
ActivityAmounts calculateActivityAmounts(List<? extends ActivitySample> samples) {
|
||||
ActivityAmount deepSleep = new ActivityAmount(ActivityKind.TYPE_DEEP_SLEEP);
|
||||
ActivityAmount lightSleep = new ActivityAmount(ActivityKind.TYPE_LIGHT_SLEEP);
|
||||
@ -53,7 +65,7 @@ class ActivityAnalysis {
|
||||
|
||||
int steps = sample.getSteps();
|
||||
if (steps > 0) {
|
||||
amount.addSteps(sample.getSteps());
|
||||
amount.addSteps(steps);
|
||||
}
|
||||
|
||||
if (previousSample != null) {
|
||||
@ -65,6 +77,22 @@ class ActivityAnalysis {
|
||||
previousAmount.addSeconds(sharedTimeDifference);
|
||||
amount.addSeconds(sharedTimeDifference);
|
||||
}
|
||||
|
||||
// add time
|
||||
if (steps > 0 && sample.getKind() == ActivityKind.TYPE_ACTIVITY) {
|
||||
if (steps > maxSpeed) {
|
||||
maxSpeed = steps;
|
||||
}
|
||||
|
||||
if (!stats.containsKey(steps)) {
|
||||
LOG.info("Adding: " + steps);
|
||||
stats.put(steps, timeDifference);
|
||||
} else {
|
||||
long time = stats.get(steps);
|
||||
LOG.info("Updating: " + steps + " " + timeDifference + time);
|
||||
stats.put(steps, timeDifference + time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousAmount = amount;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Gobbetti, Vebryn
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -102,9 +102,6 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case GBApplication.ACTION_QUIT:
|
||||
finish();
|
||||
break;
|
||||
case GBDevice.ACTION_DEVICE_CHANGED:
|
||||
GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
refreshBusyState(dev);
|
||||
@ -136,7 +133,6 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
initDates();
|
||||
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
@ -333,6 +329,8 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
return new WeekStepsChartFragment();
|
||||
case 4:
|
||||
return new LiveActivityFragment();
|
||||
case 5:
|
||||
return new SpeedZonesFragment();
|
||||
|
||||
}
|
||||
return null;
|
||||
@ -340,7 +338,11 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
// Show 5 total pages.
|
||||
// Show 5 or 6 total pages.
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice);
|
||||
if (coordinator.supportsRealtimeData()) {
|
||||
return 6;
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
|
||||
@ -357,6 +359,8 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
return getString(R.string.weekstepschart_steps_a_week);
|
||||
case 4:
|
||||
return getString(R.string.liveactivity_live_activity);
|
||||
case 5:
|
||||
return getString(R.string.stats_title);
|
||||
}
|
||||
return super.getPageTitle(position);
|
||||
}
|
||||
|
@ -0,0 +1,170 @@
|
||||
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Vebryn
|
||||
|
||||
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.charts;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.github.mikephil.charting.charts.Chart;
|
||||
import com.github.mikephil.charting.charts.HorizontalBarChart;
|
||||
import com.github.mikephil.charting.components.XAxis;
|
||||
import com.github.mikephil.charting.components.YAxis;
|
||||
import com.github.mikephil.charting.data.BarData;
|
||||
import com.github.mikephil.charting.data.BarDataSet;
|
||||
import com.github.mikephil.charting.data.BarEntry;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
|
||||
|
||||
public class SpeedZonesFragment extends AbstractChartFragment {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(SpeedZonesFragment.class);
|
||||
|
||||
private HorizontalBarChart mStatsChart;
|
||||
|
||||
@Override
|
||||
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
|
||||
List<? extends ActivitySample> samples = getSamples(db, device);
|
||||
|
||||
MySpeedZonesData mySpeedZonesData = refreshStats(samples);
|
||||
|
||||
return new MyChartsData(mySpeedZonesData);
|
||||
}
|
||||
|
||||
private MySpeedZonesData refreshStats(List<? extends ActivitySample> samples) {
|
||||
ActivityAnalysis analysis = new ActivityAnalysis();
|
||||
analysis.calculateActivityAmounts(samples);
|
||||
BarData data = new BarData();
|
||||
data.setValueTextColor(CHART_TEXT_COLOR);
|
||||
List<BarEntry> entries = new ArrayList<>();
|
||||
|
||||
ActivityUser user = new ActivityUser();
|
||||
/*double distanceFactorCm;
|
||||
if (user.getGender() == user.GENDER_MALE){
|
||||
distanceFactorCm = user.getHeightCm() * user.GENDER_MALE_DISTANCE_FACTOR / 1000;
|
||||
} else {
|
||||
distanceFactorCm = user.getHeightCm() * user.GENDER_FEMALE_DISTANCE_FACTOR / 1000;
|
||||
}*/
|
||||
|
||||
for (Map.Entry<Integer, Long> entry : analysis.stats.entrySet()) {
|
||||
entries.add(new BarEntry(entry.getKey(), entry.getValue() / 60));
|
||||
}
|
||||
|
||||
BarDataSet set = new BarDataSet(entries, "");
|
||||
set.setValueTextColor(CHART_TEXT_COLOR);
|
||||
set.setColors(getColorFor(ActivityKind.TYPE_ACTIVITY));
|
||||
//set.setDrawValues(false);
|
||||
//data.setBarWidth(0.1f);
|
||||
data.addDataSet(set);
|
||||
|
||||
return new MySpeedZonesData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateChartsnUIThread(ChartsData chartsData) {
|
||||
MyChartsData mcd = (MyChartsData) chartsData;
|
||||
mStatsChart.setData(mcd.getChartsData().getBarData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return getString(R.string.stats_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_statschart, container, false);
|
||||
|
||||
mStatsChart = (HorizontalBarChart) rootView.findViewById(R.id.statschart);
|
||||
setupStatsChart();
|
||||
|
||||
// refresh immediately instead of use refreshIfVisible(), for perceived performance
|
||||
refresh();
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void setupStatsChart() {
|
||||
mStatsChart.setBackgroundColor(BACKGROUND_COLOR);
|
||||
mStatsChart.getDescription().setTextColor(DESCRIPTION_COLOR);
|
||||
mStatsChart.setNoDataText("");
|
||||
mStatsChart.getLegend().setEnabled(false);
|
||||
mStatsChart.setTouchEnabled(false);
|
||||
mStatsChart.getDescription().setText("");
|
||||
|
||||
XAxis x = mStatsChart.getXAxis();
|
||||
x.setTextColor(CHART_TEXT_COLOR);
|
||||
|
||||
YAxis yr = mStatsChart.getAxisRight();
|
||||
yr.setTextColor(CHART_TEXT_COLOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
return super.getAllSamples(db, device, tsFrom, tsTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupLegend(Chart chart) {
|
||||
// no legend here, it is all about the steps here
|
||||
chart.getLegend().setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderCharts() {
|
||||
mStatsChart.invalidate();
|
||||
}
|
||||
|
||||
private static class MySpeedZonesData extends ChartsData {
|
||||
private final BarData barData;
|
||||
|
||||
MySpeedZonesData(BarData barData) {
|
||||
this.barData = barData;
|
||||
}
|
||||
|
||||
BarData getBarData() {
|
||||
return barData;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyChartsData extends ChartsData {
|
||||
private final MySpeedZonesData chartsData;
|
||||
|
||||
MyChartsData(MySpeedZonesData chartsData) {
|
||||
this.chartsData = chartsData;
|
||||
}
|
||||
|
||||
MySpeedZonesData getChartsData() {
|
||||
return chartsData;
|
||||
}
|
||||
}
|
||||
}
|
@ -62,7 +62,7 @@ public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapte
|
||||
if (name == null) {
|
||||
name = ai.packageName;
|
||||
}
|
||||
if (GBApplication.isBlacklisted(ai.packageName)) {
|
||||
if (GBApplication.appIsBlacklisted(ai.packageName)) {
|
||||
// sort blacklisted first by prefixing with a '!'
|
||||
name = "!" + name;
|
||||
}
|
||||
@ -94,7 +94,7 @@ public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapte
|
||||
holder.deviceAppNameLabel.setText(mNameMap.get(appInfo));
|
||||
holder.deviceImageView.setImageDrawable(appInfo.loadIcon(mPm));
|
||||
|
||||
holder.checkbox.setChecked(GBApplication.isBlacklisted(appInfo.packageName));
|
||||
holder.checkbox.setChecked(GBApplication.appIsBlacklisted(appInfo.packageName));
|
||||
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -102,9 +102,9 @@ public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapte
|
||||
CheckBox checkBox = ((CheckBox) v.findViewById(R.id.item_checkbox));
|
||||
checkBox.toggle();
|
||||
if (checkBox.isChecked()) {
|
||||
GBApplication.addToBlacklist(appInfo.packageName);
|
||||
GBApplication.addAppToBlacklist(appInfo.packageName);
|
||||
} else {
|
||||
GBApplication.removeFromBlacklist(appInfo.packageName);
|
||||
GBApplication.removeFromAppsBlacklist(appInfo.packageName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -189,6 +189,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
|
||||
public void onClick(View v) {
|
||||
Intent startIntent;
|
||||
startIntent = new Intent(context, ConfigureAlarms.class);
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
context.startActivity(startIntent);
|
||||
}
|
||||
}
|
||||
|
@ -201,8 +201,6 @@ public interface DeviceCoordinator {
|
||||
*/
|
||||
boolean supportsHeartRateMeasurement(GBDevice device);
|
||||
|
||||
int getTapString();
|
||||
|
||||
/**
|
||||
* Returns the readable name of the manufacturer.
|
||||
*/
|
||||
@ -234,4 +232,10 @@ public interface DeviceCoordinator {
|
||||
*/
|
||||
boolean supportsCalendarEvents();
|
||||
|
||||
/**
|
||||
* Indicates whether the device supports getting a stream of live data.
|
||||
* This can be live HR, steps etc.
|
||||
*/
|
||||
boolean supportsRealtimeData();
|
||||
|
||||
}
|
||||
|
@ -157,11 +157,6 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "unknown";
|
||||
@ -181,4 +176,9 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,60 @@
|
||||
/* Copyright (C) 2017 Andreas Shimokawa, João Paulo Barraca
|
||||
|
||||
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.amazfitbip;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class AmazfitBipCooordinator extends MiBand2Coordinator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipCooordinator.class);
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.AMAZFITBIP;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
try {
|
||||
BluetoothDevice device = candidate.getDevice();
|
||||
String name = device.getName();
|
||||
if (name != null && name.equalsIgnoreCase("Amazfit Bip Watch")) {
|
||||
return DeviceType.AMAZFITBIP;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.error("unable to check device support", ex);
|
||||
}
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
AmazfitBipFWInstallHandler handler = new AmazfitBipFWInstallHandler(uri, context);
|
||||
return handler.isValid() ? handler : null;
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/* Copyright (C) 2016-2017 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.amazfitbip;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.AmazfitBipFirmwareInfo;
|
||||
|
||||
public class AmazfitBipFWHelper extends AbstractMiBandFWHelper {
|
||||
|
||||
public AmazfitBipFWHelper(Uri uri, Context context) throws IOException {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
private AmazfitBipFirmwareInfo firmwareInfo;
|
||||
|
||||
@Override
|
||||
public String format(int version) {
|
||||
return AmazfitBipFirmwareInfo.toVersion(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirmwareVersion() {
|
||||
return firmwareInfo.getFirmwareVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirmware2Version() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHumanFirmwareVersion2() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] getWhitelistedFirmwareVersions() {
|
||||
return AmazfitBipFirmwareInfo.getWhitelistedVersions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFirmwareGenerallyCompatibleWith(GBDevice device) {
|
||||
return firmwareInfo.isGenerallyCompatibleWith(device);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleFirmware() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
|
||||
firmwareInfo = new AmazfitBipFirmwareInfo(wholeFirmwareBytes);
|
||||
if (!firmwareInfo.isHeaderValid()) {
|
||||
throw new IllegalArgumentException("Not a an Amazifit Bip firmware");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkValid() throws IllegalArgumentException {
|
||||
firmwareInfo.checkValid();
|
||||
}
|
||||
|
||||
public AmazfitBipFirmwareInfo getFirmwareInfo() {
|
||||
return firmwareInfo;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/* Copyright (C) 2015-2017 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.amazfitbip;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
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 AmazfitBipFWInstallHandler extends AbstractMiBandFWInstallHandler {
|
||||
AmazfitBipFWInstallHandler(Uri uri, Context context) {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
|
||||
return new AmazfitBipFWHelper(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedDeviceType(GBDevice device) {
|
||||
return device.getType() == DeviceType.AMAZFITBIP;
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/* Copyright (C) 2017 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.amazfitbip;
|
||||
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
|
||||
public class AmazfitBipIcon {
|
||||
// icons which are unsure which app they are for are suffixed with _NN
|
||||
public static final int CHAT = 0;
|
||||
public static final int PENGUIN_1 = 1;
|
||||
public static final int MI_CHAT_2 = 2;
|
||||
public static final int FACEBOOK = 3;
|
||||
public static final int TWITTER = 4;
|
||||
public static final int MI_APP_5 = 5;
|
||||
public static final int SNAPCHAT = 6;
|
||||
public static final int WHATSAPP = 7;
|
||||
public static final int RED_WHITE_FIRE_8 = 8;
|
||||
public static final int CHINESE_9 = 9;
|
||||
public static final int ALARM_CLOCK = 10;
|
||||
public static final int APP_11 = 11;
|
||||
public static final int CAMERA_12 = 12;
|
||||
public static final int CHAT_BLUE_13 = 13;
|
||||
public static final int COW_14 = 14;
|
||||
public static final int CHINESE_15 = 15;
|
||||
public static final int CHINESE_16 = 16;
|
||||
public static final int STAR_17 = 17;
|
||||
public static final int APP_18 = 18;
|
||||
public static final int CHINESE_19 = 19;
|
||||
public static final int CHINESE_20 = 20;
|
||||
public static final int CALENDAR = 21;
|
||||
public static final int FACEBOOK_MESSENGER = 22;
|
||||
public static final int WHATSAPP_CALL_23 = 23;
|
||||
public static final int LINE = 24;
|
||||
public static final int TELEGRAM = 25;
|
||||
public static final int KAKAOTALK = 26;
|
||||
public static final int SKYPE = 27;
|
||||
public static final int VKONTAKTE = 28;
|
||||
public static final int POKEMONGO = 29;
|
||||
public static final int HANGOUTS = 30;
|
||||
public static final int MI_31 = 31;
|
||||
public static final int CHINESE_32 = 32;
|
||||
public static final int CHINESE_33 = 33;
|
||||
public static final int EMAIL = 34;
|
||||
public static final int WEATHER = 35;
|
||||
public static final int HR_WARNING_36 = 36;
|
||||
|
||||
|
||||
public static int mapToIconId(NotificationType type) {
|
||||
switch (type) {
|
||||
case UNKNOWN:
|
||||
return APP_11;
|
||||
case CONVERSATIONS:
|
||||
return CHAT;
|
||||
case GENERIC_EMAIL:
|
||||
return EMAIL;
|
||||
case GENERIC_NAVIGATION:
|
||||
return APP_11;
|
||||
case GENERIC_SMS:
|
||||
return CHAT;
|
||||
case GENERIC_CALENDAR:
|
||||
return CALENDAR;
|
||||
case FACEBOOK:
|
||||
return FACEBOOK;
|
||||
case FACEBOOK_MESSENGER:
|
||||
return FACEBOOK_MESSENGER;
|
||||
case RIOT:
|
||||
return CHAT;
|
||||
case SIGNAL:
|
||||
return CHAT_BLUE_13;
|
||||
case TWITTER:
|
||||
return TWITTER;
|
||||
case TELEGRAM:
|
||||
return TELEGRAM;
|
||||
case WHATSAPP:
|
||||
return WHATSAPP;
|
||||
case GENERIC_ALARM_CLOCK:
|
||||
return ALARM_CLOCK;
|
||||
}
|
||||
return APP_11;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/* Copyright (C) 2015-2017 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.amazfitbip;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class AmazfitBipService {
|
||||
public static final UUID UUID_CHARACTERISTIC_WEATHER = UUID.fromString("0000000e-0000-3512-2118-0009af100700");
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
/* Copyright (C) 2017 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.amazfitbip;
|
||||
|
||||
|
||||
public class AmazfitBipWeatherConditions {
|
||||
public static final byte CLEAR_SKY = 0;
|
||||
public static final byte SCATTERED_CLOUDS = 1;
|
||||
public static final byte CLOUDY = 2;
|
||||
public static final byte RAIN_WITH_SUN = 3;
|
||||
public static final byte THUNDERSTORM = 4;
|
||||
public static final byte HAIL = 5;
|
||||
public static final byte RAIN_AND_SNOW = 6;
|
||||
public static final byte LIGHT_RAIN = 7;
|
||||
public static final byte MEDIUM_RAIN = 8;
|
||||
public static final byte HEAVY_RAIN = 9;
|
||||
public static final byte EXTREME_RAIN = 10;
|
||||
public static final byte SUPER_EXTREME_RAIN = 11;
|
||||
public static final byte TORRENTIAL_RAIN = 12;
|
||||
public static final byte SNOW_AND_SUN = 13;
|
||||
public static final byte LIGHT_SNOW = 14;
|
||||
public static final byte MEDIUM_SNOW = 15;
|
||||
public static final byte HEAVY_SNOW = 16;
|
||||
public static final byte EXTREME_SNOW = 17;
|
||||
public static final byte MIST = 18;
|
||||
public static final byte DRIZZLE = 19;
|
||||
public static final byte WIND_AND_RAIN = 20;
|
||||
// 21- various types of rain
|
||||
|
||||
public static byte mapToAmazfitBipWeatherCode(int openWeatherMapCondition) {
|
||||
// openweathermap.org conditions:
|
||||
// http://openweathermap.org/weather-conditions
|
||||
switch (openWeatherMapCondition) {
|
||||
//Group 2xx: Thunderstorm
|
||||
case 200: //thunderstorm with light rain: //11d
|
||||
case 201: //thunderstorm with rain: //11d
|
||||
case 202: //thunderstorm with heavy rain: //11d
|
||||
case 210: //light thunderstorm:: //11d
|
||||
case 211: //thunderstorm: //11d
|
||||
case 230: //thunderstorm with light drizzle: //11d
|
||||
case 231: //thunderstorm with drizzle: //11d
|
||||
case 232: //thunderstorm with heavy drizzle: //11d
|
||||
case 212: //heavy thunderstorm: //11d
|
||||
case 221: //ragged thunderstorm: //11d
|
||||
return THUNDERSTORM;
|
||||
//Group 3xx: Drizzle
|
||||
case 300: //light intensity drizzle: //09d
|
||||
case 301: //drizzle: //09d
|
||||
case 302: //heavy intensity drizzle: //09d
|
||||
case 310: //light intensity drizzle rain: //09d
|
||||
case 311: //drizzle rain: //09d
|
||||
case 312: //heavy intensity drizzle rain: //09d
|
||||
case 313: //shower rain and drizzle: //09d
|
||||
case 314: //heavy shower rain and drizzle: //09d
|
||||
case 321: //shower drizzle: //09d
|
||||
return DRIZZLE;
|
||||
//Group 5xx: Rain
|
||||
case 500: //light rain: //10d
|
||||
return LIGHT_RAIN;
|
||||
case 501: //moderate rain: //10d
|
||||
return MEDIUM_RAIN;
|
||||
case 502: //heavy intensity rain: //10d
|
||||
return HEAVY_RAIN;
|
||||
case 503: //very heavy rain: //10d
|
||||
return EXTREME_RAIN;
|
||||
case 504: //extreme rain: //10d
|
||||
return TORRENTIAL_RAIN;
|
||||
case 511: //freezing rain: //13d
|
||||
return MEDIUM_RAIN;
|
||||
case 520: //light intensity shower rain: //09d
|
||||
return LIGHT_RAIN;
|
||||
case 521: //shower rain: //09d
|
||||
return MEDIUM_RAIN;
|
||||
case 522: //heavy intensity shower rain: //09d
|
||||
return HEAVY_RAIN;
|
||||
case 531: //ragged shower rain: //09d
|
||||
return MEDIUM_RAIN;
|
||||
//Group 6xx: Snow
|
||||
case 600: //light snow: //[[file:13d.png]]
|
||||
return LIGHT_SNOW;
|
||||
case 601: //snow: //[[file:13d.png]]
|
||||
return MEDIUM_SNOW;
|
||||
case 602: //heavy snow: //[[file:13d.png]]
|
||||
return HEAVY_SNOW;
|
||||
case 611: //sleet: //[[file:13d.png]]
|
||||
case 612: //shower sleet: //[[file:13d.png]]
|
||||
case 615: //light rain and snow: //[[file:13d.png]]
|
||||
case 616: //rain and snow: //[[file:13d.png]]
|
||||
case 620: //light shower snow: //[[file:13d.png]]
|
||||
case 621: //shower snow: //[[file:13d.png]]
|
||||
case 622: //heavy shower snow: //[[file:13d.png]]
|
||||
return RAIN_AND_SNOW;
|
||||
|
||||
//Group 7xx: Atmosphere
|
||||
case 701: //mist: //[[file:50d.png]]
|
||||
case 711: //smoke: //[[file:50d.png]]
|
||||
case 721: //haze: //[[file:50d.png]]
|
||||
case 731: //sandcase dust whirls: //[[file:50d.png]]
|
||||
case 741: //fog: //[[file:50d.png]]
|
||||
case 751: //sand: //[[file:50d.png]]
|
||||
case 761: //dust: //[[file:50d.png]]
|
||||
case 762: //volcanic ash: //[[file:50d.png]]
|
||||
case 771: //squalls: //[[file:50d.png]]
|
||||
return MIST;
|
||||
case 781: //tornado: //[[file:50d.png]]
|
||||
case 900: //tornado
|
||||
return WIND_AND_RAIN;
|
||||
//Group 800: Clear
|
||||
case 800: //clear sky: //[[file:01d.png]] [[file:01n.png]]
|
||||
return CLEAR_SKY;
|
||||
//Group 80x: Clouds
|
||||
case 801: //few clouds: //[[file:02d.png]] [[file:02n.png]]
|
||||
case 802: //scattered clouds: //[[file:03d.png]] [[file:03d.png]]
|
||||
case 803: //broken clouds: //[[file:04d.png]] [[file:03d.png]]
|
||||
return SCATTERED_CLOUDS;
|
||||
case 804: //overcast clouds: //[[file:04d.png]] [[file:04d.png]]
|
||||
return CLOUDY;
|
||||
//Group 90x: Extreme
|
||||
case 901: //tropical storm
|
||||
return WIND_AND_RAIN;
|
||||
case 903: //cold
|
||||
case 904: //hot
|
||||
case 905: //windy
|
||||
return 0;
|
||||
case 906: //hail
|
||||
return HAIL;
|
||||
//Group 9xx: Additional
|
||||
case 951: //calm
|
||||
case 952: //light breeze
|
||||
case 953: //gentle breeze
|
||||
case 954: //moderate breeze
|
||||
case 955: //fresh breeze
|
||||
case 956: //strong breeze
|
||||
case 957: //high windcase near gale
|
||||
case 958: //gale
|
||||
case 959: //severe gale
|
||||
case 960: //storm
|
||||
case 961: //violent storm
|
||||
case 902: //hurricane
|
||||
case 962: //hurricane
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -79,7 +79,6 @@ public final class HPlusConstants {
|
||||
public static final byte ARG_FINDME_ON = 0x01;
|
||||
public static final byte ARG_FINDME_OFF = 0x02;
|
||||
|
||||
public static final byte CMD_GET_VERSION = 0x17;
|
||||
public static final byte CMD_SET_END = 0x4f;
|
||||
public static final byte CMD_SET_INCOMING_CALL_NUMBER = 0x23;
|
||||
public static final byte CMD_SET_ALLDAY_HRM = 0x35;
|
||||
@ -89,7 +88,8 @@ public final class HPlusConstants {
|
||||
public static final byte CMD_SET_SIT_INTERVAL = 0x51;
|
||||
public static final byte CMD_SET_HEARTRATE_STATE = 0x32;
|
||||
|
||||
//Actions to device
|
||||
//GET messages
|
||||
public static final byte CMD_GET_VERSION = 0x17;
|
||||
public static final byte CMD_GET_ACTIVE_DAY = 0x27;
|
||||
public static final byte CMD_GET_DAY_DATA = 0x15;
|
||||
public static final byte CMD_GET_SLEEP = 0x19;
|
||||
@ -122,7 +122,7 @@ public final class HPlusConstants {
|
||||
public static final byte DATA_SLEEP = 0x1A;
|
||||
public static final byte DATA_VERSION = 0x18;
|
||||
public static final byte DATA_VERSION1 = 0x2E;
|
||||
|
||||
public static final byte DATA_DAY_UNKNOWN = 0x52; //To be defined
|
||||
public static final byte DATA_UNKNOWN = 0x4d;
|
||||
|
||||
public static final String PREF_HPLUS_SCREENTIME = "hplus_screentime";
|
||||
|
@ -92,6 +92,11 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.HPLUS;
|
||||
@ -147,11 +152,6 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
return R.string.tap_connected_device_for_activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Zeblaze";
|
||||
@ -289,6 +289,6 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
||||
}
|
||||
|
||||
public static boolean getUnicodeSupport(String address){
|
||||
return (prefs.getBoolean(HPlusConstants.PREF_HPLUS_UNICODE, false));
|
||||
return (prefs.getBoolean(HPlusConstants.PREF_HPLUS_UNICODE + "_" + address, false));
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
|
||||
*/
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
@ -57,7 +58,7 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider<HPlusHealt
|
||||
}
|
||||
|
||||
public int normalizeType(int rawType) {
|
||||
switch (rawType){
|
||||
switch (rawType) {
|
||||
case HPlusDataRecord.TYPE_DAY_SLOT:
|
||||
case HPlusDataRecord.TYPE_DAY_SUMMARY:
|
||||
case HPlusDataRecord.TYPE_REALTIME:
|
||||
@ -141,6 +142,58 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider<HPlusHealt
|
||||
|
||||
List<HPlusHealthActivityOverlay> overlayRecords = qb.build().list();
|
||||
|
||||
|
||||
|
||||
//Apply Overlays
|
||||
for (HPlusHealthActivityOverlay overlay : overlayRecords) {
|
||||
|
||||
//Create fake events to improve activity counters if there are no events around the overlay
|
||||
//timestamp boundaries
|
||||
//Insert one before, one at the beginning, one at the end, and one 1s after.
|
||||
insertVirtualItem(samples, Math.max(overlay.getTimestampFrom() - 1, timestamp_from), overlay.getDeviceId(), overlay.getUserId());
|
||||
insertVirtualItem(samples, Math.max(overlay.getTimestampFrom(), timestamp_from), overlay.getDeviceId(), overlay.getUserId());
|
||||
insertVirtualItem(samples, Math.min(overlay.getTimestampTo() - 1, timestamp_to - 1), overlay.getDeviceId(), overlay.getUserId());
|
||||
insertVirtualItem(samples, Math.min(overlay.getTimestampTo(), timestamp_to), overlay.getDeviceId(), overlay.getUserId());
|
||||
}
|
||||
|
||||
Collections.sort(samples, new Comparator<HPlusHealthActivitySample>() {
|
||||
public int compare(HPlusHealthActivitySample one, HPlusHealthActivitySample other) {
|
||||
return one.getTimestamp() - other.getTimestamp();
|
||||
}
|
||||
});
|
||||
|
||||
//Apply Overlays
|
||||
for (HPlusHealthActivityOverlay overlay : overlayRecords) {
|
||||
|
||||
long nonSleepTimeEnd = 0;
|
||||
for (HPlusHealthActivitySample sample : samples) {
|
||||
if (sample.getRawKind() == ActivityKind.TYPE_NOT_WORN)
|
||||
continue;
|
||||
|
||||
if (sample.getTimestamp() >= overlay.getTimestampFrom() && sample.getTimestamp() < overlay.getTimestampTo()) {
|
||||
if (overlay.getRawKind() == ActivityKind.TYPE_NOT_WORN || overlay.getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP || overlay.getRawKind() == ActivityKind.TYPE_DEEP_SLEEP) {
|
||||
if (sample.getRawKind() == HPlusDataRecord.TYPE_DAY_SLOT && sample.getSteps() > 0){
|
||||
nonSleepTimeEnd = sample.getTimestamp() + 10 * 60; // 10 minutes
|
||||
continue;
|
||||
}else if(sample.getRawKind() == HPlusDataRecord.TYPE_REALTIME && sample.getTimestamp() <= nonSleepTimeEnd){
|
||||
continue;
|
||||
}
|
||||
|
||||
if (overlay.getRawKind() == ActivityKind.TYPE_NOT_WORN)
|
||||
sample.setHeartRate(0);
|
||||
|
||||
if (sample.getRawKind() != ActivityKind.TYPE_NOT_WORN)
|
||||
sample.setRawKind(overlay.getRawKind());
|
||||
|
||||
sample.setRawIntensity(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Fix Step counters
|
||||
//Todays sample steps will come from the Day Slots messages
|
||||
//Historical steps will be provided by Day Summaries messages
|
||||
//This will allow both week and current day results to be consistent
|
||||
@ -154,8 +207,8 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider<HPlusHealt
|
||||
int stepsTodayCount = 0;
|
||||
HPlusHealthActivitySample lastSample = null;
|
||||
|
||||
for(HPlusHealthActivitySample sample: samples){
|
||||
if(sample.getTimestamp() >= today.getTimeInMillis() / 1000){
|
||||
for (HPlusHealthActivitySample sample: samples) {
|
||||
if (sample.getTimestamp() >= today.getTimeInMillis() / 1000) {
|
||||
|
||||
/**Strategy is:
|
||||
* Calculate max steps from realtime messages
|
||||
@ -170,7 +223,7 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider<HPlusHealt
|
||||
|
||||
sample.setSteps(ActivitySample.NOT_MEASURED);
|
||||
lastSample = sample;
|
||||
}else{
|
||||
} else {
|
||||
if (sample.getRawKind() != HPlusDataRecord.TYPE_DAY_SUMMARY) {
|
||||
sample.setSteps(ActivitySample.NOT_MEASURED);
|
||||
}
|
||||
@ -180,35 +233,8 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider<HPlusHealt
|
||||
if(lastSample != null)
|
||||
lastSample.setSteps(Math.max(stepsTodayCount, stepsTodayMax));
|
||||
|
||||
for (HPlusHealthActivityOverlay overlay : overlayRecords) {
|
||||
|
||||
//Create fake events to improve activity counters if there are no events around the overlay
|
||||
//timestamp boundaries
|
||||
//Insert one before, one at the beginning, one at the end, and one 1s after.
|
||||
insertVirtualItem(samples, Math.max(overlay.getTimestampFrom() - 1, timestamp_from), overlay.getDeviceId(), overlay.getUserId());
|
||||
insertVirtualItem(samples, Math.max(overlay.getTimestampFrom(), timestamp_from), overlay.getDeviceId(), overlay.getUserId());
|
||||
insertVirtualItem(samples, Math.min(overlay.getTimestampTo() - 1, timestamp_to - 1), overlay.getDeviceId(), overlay.getUserId());
|
||||
insertVirtualItem(samples, Math.min(overlay.getTimestampTo(), timestamp_to), overlay.getDeviceId(), overlay.getUserId());
|
||||
|
||||
for (HPlusHealthActivitySample sample : samples) {
|
||||
|
||||
if (sample.getTimestamp() >= overlay.getTimestampFrom() && sample.getTimestamp() < overlay.getTimestampTo()) {
|
||||
if(overlay.getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP || overlay.getRawKind() == ActivityKind.TYPE_DEEP_SLEEP)
|
||||
sample.setRawIntensity(10);
|
||||
|
||||
sample.setRawKind(overlay.getRawKind());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
detachFromSession();
|
||||
|
||||
Collections.sort(samples, new Comparator<HPlusHealthActivitySample>() {
|
||||
public int compare(HPlusHealthActivitySample one, HPlusHealthActivitySample other) {
|
||||
return one.getTimestamp() - other.getTimestamp();
|
||||
}
|
||||
});
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
|
@ -99,12 +99,6 @@ public class LiveviewCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
//TODO: changeme
|
||||
return R.string.tap_connected_device_for_activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Sony Ericsson";
|
||||
@ -125,6 +119,11 @@ public class LiveviewCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
// nothing to delete, yet
|
||||
|
@ -82,7 +82,7 @@ public abstract class AbstractMiBandFWInstallHandler implements InstallHandler {
|
||||
}
|
||||
|
||||
GenericItem fwItem = new GenericItem(mContext.getString(R.string.miband_installhandler_miband_firmware, helper.getHumanFirmwareVersion()));
|
||||
fwItem.setIcon(R.drawable.ic_device_miband);
|
||||
fwItem.setIcon(device.getType().getIcon());
|
||||
|
||||
if (!helper.isFirmwareGenerallyCompatibleWith(device)) {
|
||||
fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_incompatible_version));
|
||||
|
@ -0,0 +1,23 @@
|
||||
/* Copyright (C) 2017 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.miband;
|
||||
|
||||
public enum DoNotDisturb {
|
||||
OFF,
|
||||
AUTOMATIC,
|
||||
SCHEDULED
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer, José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -28,8 +28,12 @@ import android.support.annotation.NonNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -117,6 +121,92 @@ public class MiBand2Coordinator extends MiBandCoordinator {
|
||||
return prefs.getBoolean(MiBandConst.PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT, true);
|
||||
}
|
||||
|
||||
public static Set<String> getDisplayItems() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getStringSet(MiBandConst.PREF_MI2_DISPLAY_ITEMS, null);
|
||||
}
|
||||
|
||||
public static boolean getGoalNotification() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getBoolean(MiBandConst.PREF_MI2_GOAL_NOTIFICATION, false);
|
||||
}
|
||||
|
||||
public static boolean getRotateWristToSwitchInfo() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getBoolean(MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO, false);
|
||||
}
|
||||
|
||||
public static boolean getInactivityWarnings() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getBoolean(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS, false);
|
||||
}
|
||||
|
||||
public static int getInactivityWarningsThreshold() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getInt(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD, 60);
|
||||
}
|
||||
|
||||
public static boolean getInactivityWarningsDnd() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getBoolean(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND, false);
|
||||
}
|
||||
|
||||
public static Date getInactivityWarningsStart() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_START, "06:00");
|
||||
}
|
||||
|
||||
public static Date getInactivityWarningsEnd() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_END, "22:00");
|
||||
}
|
||||
|
||||
public static Date getInactivityWarningsDndStart() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND_START, "12:00");
|
||||
}
|
||||
|
||||
public static Date getInactivityWarningsDndEnd() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND_END, "14:00");
|
||||
}
|
||||
|
||||
public static Date getDoNotDisturbStart() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_DO_NOT_DISTURB_START, "01:00");
|
||||
}
|
||||
|
||||
public static Date getDoNotDisturbEnd() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_DO_NOT_DISTURB_END, "06:00");
|
||||
}
|
||||
|
||||
public static Date getTimePreference(String key, String defaultValue) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
String time = prefs.getString(key, defaultValue);
|
||||
|
||||
DateFormat df = new SimpleDateFormat("HH:mm");
|
||||
try {
|
||||
return df.parse(time);
|
||||
} catch(Exception e) {
|
||||
LOG.error("Unexpected exception in MiBand2Coordinator.getTime: " + e.getMessage());
|
||||
}
|
||||
|
||||
return new Date();
|
||||
}
|
||||
|
||||
public static DoNotDisturb getDoNotDisturb(Context context) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
|
||||
String dndOff = context.getString(R.string.p_off);
|
||||
String dndAutomatic = context.getString(R.string.p_automatic);
|
||||
String dndScheduled = context.getString(R.string.p_scheduled);
|
||||
|
||||
String pref = prefs.getString(MiBandConst.PREF_MI2_DO_NOT_DISTURB, dndOff);
|
||||
|
||||
if (dndAutomatic.equals(pref)) {
|
||||
return DoNotDisturb.AUTOMATIC;
|
||||
} else if (dndScheduled.equals(pref)) {
|
||||
return DoNotDisturb.SCHEDULED;
|
||||
}
|
||||
|
||||
return DoNotDisturb.OFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
MiBand2FWInstallHandler handler = new MiBand2FWInstallHandler(uri, context);
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer, JohnnySun, Uwe Hermann
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, JohnnySun,
|
||||
José Rebelo, Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -53,7 +54,6 @@ public class MiBand2Service {
|
||||
public static final int ALERT_LEVEL_MESSAGE = 1;
|
||||
public static final int ALERT_LEVEL_PHONE_CALL = 2;
|
||||
public static final int ALERT_LEVEL_VIBRATE_ONLY = 3;
|
||||
public static final int ALERT_LEVEL_CUSTOM = 0xfa; // followed by another uin8 to select the actual icon
|
||||
|
||||
// set metric distance
|
||||
// set 12 hour time mode
|
||||
@ -145,6 +145,19 @@ public class MiBand2Service {
|
||||
|
||||
public static final byte ICON_HIGH_PRIORITY = 0x7;
|
||||
|
||||
public static byte ENDPOINT_DISPLAY_ITEMS = 0x0a;
|
||||
|
||||
public static byte DISPLAY_ITEM_BIT_CLOCK = 0x01;
|
||||
public static byte DISPLAY_ITEM_BIT_STEPS = 0x02;
|
||||
public static byte DISPLAY_ITEM_BIT_DISTANCE = 0x04;
|
||||
public static byte DISPLAY_ITEM_BIT_CALORIES= 0x08;
|
||||
public static byte DISPLAY_ITEM_BIT_HEART_RATE = 0x10;
|
||||
public static byte DISPLAY_ITEM_BIT_BATTERY = 0x20;
|
||||
|
||||
// Second byte must be a bitwise OR combination of the above
|
||||
// The clock can't be disabled
|
||||
public static int SCREEN_CHANGE_BYTE = 1;
|
||||
public static final byte[] COMMAND_CHANGE_SCREENS = new byte[]{ENDPOINT_DISPLAY_ITEMS, DISPLAY_ITEM_BIT_CLOCK, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
|
||||
|
||||
public static byte ENDPOINT_DISPLAY = 0x06;
|
||||
|
||||
@ -154,9 +167,42 @@ public class MiBand2Service {
|
||||
public static final byte[] DATEFORMAT_TIME_24_HOURS = new byte[] {ENDPOINT_DISPLAY, 0x02, 0x0, 0x1 };
|
||||
public static final byte[] COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x01};
|
||||
public static final byte[] COMMAND_DISABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x00};
|
||||
public static final byte[] COMMAND_ENABLE_GOAL_NOTIFICATION = new byte[]{ENDPOINT_DISPLAY, 0x06, 0x00, 0x01};
|
||||
public static final byte[] COMMAND_DISABLE_GOAL_NOTIFICATION = new byte[]{ENDPOINT_DISPLAY, 0x06, 0x00, 0x00};
|
||||
public static final byte[] COMMAND_ENABLE_ROTATE_WRIST_TO_SWITCH_INFO = new byte[]{ENDPOINT_DISPLAY, 0x0d, 0x00, 0x01};
|
||||
public static final byte[] COMMAND_DISABLE_ROTATE_WRIST_TO_SWITCH_INFO = new byte[]{ENDPOINT_DISPLAY, 0x0d, 0x00, 0x00};
|
||||
public static final byte[] COMMAND_ENABLE_DISPLAY_CALLER = new byte[]{ENDPOINT_DISPLAY, 0x10, 0x00, 0x00, 0x01};
|
||||
public static final byte[] COMMAND_DISABLE_DISPLAY_CALLER = new byte[]{ENDPOINT_DISPLAY, 0x10, 0x00, 0x00, 0x00};
|
||||
public static final byte[] DISPLAY_XXX = new byte[] {ENDPOINT_DISPLAY, 0x03, 0x0, 0x0 };
|
||||
public static final byte[] DISPLAY_YYY = new byte[] {ENDPOINT_DISPLAY, 0x10, 0x0, 0x1, 0x1 };
|
||||
|
||||
// The third byte controls the threshold, in minutes
|
||||
// The last 8 bytes represent 2 separate time intervals for the inactivity warnings
|
||||
// If there is no do not disturb interval, the last 4 bytes (the second interval) are 0
|
||||
// and only the first interval of the command is used
|
||||
public static int INACTIVITY_WARNINGS_THRESHOLD = 2;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_1_START_HOURS = 4;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_1_START_MINUTES = 5;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS = 6;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES = 7;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_2_START_HOURS = 8;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_2_START_MINUTES = 9;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_2_END_HOURS = 10;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_2_END_MINUTES = 11;
|
||||
public static final byte[] COMMAND_ENABLE_INACTIVITY_WARNINGS = new byte[] { 0x08, 0x01, 0x3c, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
public static final byte[] COMMAND_DISABLE_INACTIVITY_WARNINGS = new byte[] { 0x08, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
public static byte ENDPOINT_DND = 0x09;
|
||||
|
||||
public static final byte[] COMMAND_DO_NOT_DISTURB_AUTOMATIC = new byte[] { ENDPOINT_DND, (byte) 0x83 };
|
||||
public static final byte[] COMMAND_DO_NOT_DISTURB_OFF = new byte[] { ENDPOINT_DND, (byte) 0x82 };
|
||||
public static final byte[] COMMAND_DO_NOT_DISTURB_SCHEDULED = new byte[] { ENDPOINT_DND, (byte) 0x81, 0x01, 0x00, 0x06, 0x00 };
|
||||
// The 4 last bytes set the start and end time in 24h format
|
||||
public static byte DND_BYTE_START_HOURS = 2;
|
||||
public static byte DND_BYTE_START_MINUTES = 3;
|
||||
public static byte DND_BYTE_END_HOURS = 4;
|
||||
public static byte DND_BYTE_END_MINUTES = 5;
|
||||
|
||||
public static final byte RESPONSE = 0x10;
|
||||
|
||||
public static final byte SUCCESS = 0x01;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Christian
|
||||
Fischer, Daniele Gobbetti, Szymon Tomasz Stefanek
|
||||
Fischer, Daniele Gobbetti, José Rebelo, Szymon Tomasz Stefanek
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -35,8 +35,30 @@ public final class MiBandConst {
|
||||
public static final String PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION = "mi_hr_sleep_detection";
|
||||
public static final String PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS = "mi_device_time_offset_hours";
|
||||
public static final String PREF_MI2_DATEFORMAT = "mi2_dateformat";
|
||||
public static final String PREF_MI2_GOAL_NOTIFICATION = "mi2_goal_notification";
|
||||
public static final String PREF_MI2_DISPLAY_ITEMS = "mi2_display_items";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_CLOCK = "clock";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_STEPS = "steps";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_DISTANCE = "distance";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_CALORIES = "calories";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_HEART_RATE = "heart_rate";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_BATTERY = "battery";
|
||||
public static final String PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT = "mi2_activate_display_on_lift_wrist";
|
||||
public static final String PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO = "mi2_rotate_wrist_to_switch_info";
|
||||
public static final String PREF_MI2_ENABLE_TEXT_NOTIFICATIONS = "mi2_enable_text_notifications";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB = "mi2_do_not_disturb";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_OFF = "off";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_AUTOMATIC = "automatic";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_SCHEDULED = "scheduled";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_START = "mi2_do_not_disturb_start";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_END = "mi2_do_not_disturb_end";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS = "mi2_inactivity_warnings";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD = "mi2_inactivity_warnings_threshold";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_START = "mi2_inactivity_warnings_start";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_END = "mi2_inactivity_warnings_end";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_DND = "mi2_inactivity_warnings_dnd";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_DND_START = "mi2_inactivity_warnings_dnd_start";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_DND_END = "mi2_inactivity_warnings_dnd_end";
|
||||
public static final String PREF_MIBAND_SETUP_BT_PAIRING = "mi_setup_bt_pairing";
|
||||
|
||||
|
||||
|
@ -151,11 +151,6 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
return R.string.tap_connected_device_for_activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Xiaomi";
|
||||
@ -176,6 +171,11 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean hasValidUserInfo() {
|
||||
String dummyMacAddress = MiBandService.MAC_ADDRESS_FILTER_1_1A + ":00:00:00";
|
||||
try {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Christian
|
||||
Fischer, Daniele Gobbetti, Szymon Tomasz Stefanek
|
||||
Fischer, Daniele Gobbetti, José Rebelo, Szymon Tomasz Stefanek
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -34,12 +34,28 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_ALARM_CLOCK;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_INCOMING_CALL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DATEFORMAT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DISPLAY_ITEMS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_OFF;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_SCHEDULED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ENABLE_TEXT_NOTIFICATIONS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_GOAL_NOTIFICATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ADDRESS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR;
|
||||
@ -57,6 +73,8 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
|
||||
addTryListeners();
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
|
||||
final Preference enableHeartrateSleepSupport = findPreference(PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION);
|
||||
enableHeartrateSleepSupport.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
@ -66,6 +84,20 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
}
|
||||
});
|
||||
|
||||
final Preference goalNotification = findPreference(PREF_MI2_GOAL_NOTIFICATION);
|
||||
goalNotification.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_GOAL_NOTIFICATION);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference setDateFormat = findPreference(PREF_MI2_DATEFORMAT);
|
||||
setDateFormat.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
@ -80,6 +112,20 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
}
|
||||
});
|
||||
|
||||
final Preference displayPages = findPreference(PREF_MI2_DISPLAY_ITEMS);
|
||||
displayPages.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference activateDisplayOnLift = findPreference(PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT);
|
||||
activateDisplayOnLift.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
@ -94,6 +140,170 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
}
|
||||
});
|
||||
|
||||
final Preference rotateWristCycleInfo = findPreference(PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO);
|
||||
rotateWristCycleInfo.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarnings = findPreference(PREF_MI2_INACTIVITY_WARNINGS);
|
||||
inactivityWarnings.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarningsThreshold = findPreference(PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD);
|
||||
inactivityWarningsThreshold.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarningsStart = findPreference(PREF_MI2_INACTIVITY_WARNINGS_START);
|
||||
inactivityWarningsStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarningsEnd = findPreference(PREF_MI2_INACTIVITY_WARNINGS_END);
|
||||
inactivityWarningsEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarningsDnd = findPreference(PREF_MI2_INACTIVITY_WARNINGS_DND);
|
||||
inactivityWarningsDnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS_DND);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarningsDndStart = findPreference(PREF_MI2_INACTIVITY_WARNINGS_DND_START);
|
||||
inactivityWarningsDndStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS_DND_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarningsDndEnd = findPreference(PREF_MI2_INACTIVITY_WARNINGS_DND_END);
|
||||
inactivityWarningsDndEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS_DND_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
String doNotDisturbState = prefs.getString(MiBandConst.PREF_MI2_DO_NOT_DISTURB, PREF_MI2_DO_NOT_DISTURB_OFF);
|
||||
boolean doNotDisturbScheduled = doNotDisturbState.equals(PREF_MI2_DO_NOT_DISTURB_SCHEDULED);
|
||||
|
||||
final Preference doNotDisturbStart = findPreference(PREF_MI2_DO_NOT_DISTURB_START);
|
||||
doNotDisturbStart.setEnabled(doNotDisturbScheduled);
|
||||
doNotDisturbStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DO_NOT_DISTURB_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference doNotDisturbEnd = findPreference(PREF_MI2_DO_NOT_DISTURB_END);
|
||||
doNotDisturbEnd.setEnabled(doNotDisturbScheduled);
|
||||
doNotDisturbEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DO_NOT_DISTURB_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference doNotDisturb = findPreference(PREF_MI2_DO_NOT_DISTURB);
|
||||
doNotDisturb.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean scheduled = PREF_MI2_DO_NOT_DISTURB_SCHEDULED.equals(newVal.toString());
|
||||
|
||||
doNotDisturbStart.setEnabled(scheduled);
|
||||
doNotDisturbEnd.setEnabled(scheduled);
|
||||
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DO_NOT_DISTURB);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference fitnessGoal = findPreference(ActivityUser.PREF_USER_STEPS_GOAL);
|
||||
fitnessGoal.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
@ -165,6 +375,7 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
prefKeys.add(PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR);
|
||||
prefKeys.add(PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS);
|
||||
prefKeys.add(PREF_MI2_ENABLE_TEXT_NOTIFICATIONS);
|
||||
prefKeys.add(PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD);
|
||||
prefKeys.add(getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_ALARM_CLOCK));
|
||||
prefKeys.add(getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_INCOMING_CALL));
|
||||
|
||||
|
@ -137,11 +137,6 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
|
||||
return PebbleUtils.hasHRM(device.getModel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
return R.string.tap_connected_device_for_app_mananger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Pebble";
|
||||
@ -161,4 +156,9 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
|
||||
public boolean supportsCalendarEvents() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -37,11 +37,13 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
public class PebbleHealthSampleProvider extends AbstractSampleProvider<PebbleHealthActivitySample> {
|
||||
public static final int TYPE_LIGHT_SLEEP = 1;
|
||||
public static final int TYPE_DEEP_SLEEP = 2;
|
||||
public static final int TYPE_LIGHT_NAP = 3; //probably
|
||||
public static final int TYPE_DEEP_NAP = 4; //probably
|
||||
public static final int TYPE_WALK = 5; //probably
|
||||
public static final int TYPE_LIGHT_NAP = 3;
|
||||
public static final int TYPE_DEEP_NAP = 4;
|
||||
public static final int TYPE_WALK = 5;
|
||||
public static final int TYPE_RUN = 6;
|
||||
public static final int TYPE_ACTIVITY = -1;
|
||||
|
||||
|
||||
protected final float movementDivisor = 8000f;
|
||||
|
||||
public PebbleHealthSampleProvider(GBDevice device, DaoSession session) {
|
||||
@ -114,6 +116,8 @@ public class PebbleHealthSampleProvider extends AbstractSampleProvider<PebbleHea
|
||||
case TYPE_LIGHT_SLEEP:
|
||||
return ActivityKind.TYPE_LIGHT_SLEEP;
|
||||
case TYPE_ACTIVITY:
|
||||
case TYPE_WALK:
|
||||
case TYPE_RUN:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
default:
|
||||
return ActivityKind.TYPE_UNKNOWN;
|
||||
|
@ -100,11 +100,6 @@ public class VibratissimoCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
return R.string.tap_connected_device_for_vibration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Amor AG";
|
||||
@ -125,6 +120,11 @@ public class VibratissimoCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false; // hmmm well, it has a temperature sensor :D
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
// nothing to delete, yet
|
||||
|
@ -34,7 +34,6 @@ import java.util.List;
|
||||
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncState;
|
||||
|
@ -48,6 +48,12 @@ public class MusicPlaybackReceiver extends BroadcastReceiver {
|
||||
MusicStateSpec stateSpec = new MusicStateSpec(lastStateSpec);
|
||||
|
||||
Bundle incomingBundle = intent.getExtras();
|
||||
|
||||
if (incomingBundle == null) {
|
||||
LOG.warn("Not processing incoming null bundle.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (String key : incomingBundle.keySet()) {
|
||||
Object incoming = incomingBundle.get(key);
|
||||
if (incoming instanceof String && "artist".equals(key)) {
|
||||
|
@ -18,7 +18,6 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.externalevents;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
@ -30,17 +29,19 @@ import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.MediaSession;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.support.v7.app.NotificationCompat;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -76,7 +77,7 @@ public class NotificationListener extends NotificationListenerService {
|
||||
private LimitedQueue mActionLookup = new LimitedQueue(16);
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@SuppressLint("NewApi")
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
@ -102,7 +103,7 @@ public class NotificationListener extends NotificationListenerService {
|
||||
} else {
|
||||
// ACTION_MUTE
|
||||
LOG.info("going to mute " + sbn.getPackageName());
|
||||
GBApplication.addToBlacklist(sbn.getPackageName());
|
||||
GBApplication.addAppToBlacklist(sbn.getPackageName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,16 +178,8 @@ public class NotificationListener extends NotificationListenerService {
|
||||
|
||||
@Override
|
||||
public void onNotificationPosted(StatusBarNotification sbn) {
|
||||
/*
|
||||
* return early if DeviceCommunicationService is not running,
|
||||
* else the service would get started every time we get a notification.
|
||||
* unfortunately we cannot enable/disable NotificationListener at runtime like we do with
|
||||
* broadcast receivers because it seems to invalidate the permissions that are
|
||||
* necessary for NotificationListenerService
|
||||
*/
|
||||
if (!isServiceRunning()) {
|
||||
if (shouldIgnore(sbn))
|
||||
return;
|
||||
}
|
||||
|
||||
switch (GBApplication.getGrantedInterruptionFilter()) {
|
||||
case NotificationManager.INTERRUPTION_FILTER_ALL:
|
||||
@ -201,53 +194,8 @@ public class NotificationListener extends NotificationListenerService {
|
||||
|
||||
String source = sbn.getPackageName();
|
||||
Notification notification = sbn.getNotification();
|
||||
|
||||
if (handleMediaSessionNotification(notification))
|
||||
return;
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
|
||||
PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
if (powermanager.isScreenOn()) {
|
||||
// LOG.info("Not forwarding notification, screen seems to be on and settings do not allow this");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) {
|
||||
// LOG.info("Not forwarding notification, FLAG_ONGOING_EVENT is set. Notification flags: " + notification.flags);
|
||||
return;
|
||||
}
|
||||
|
||||
/* do not display messages from "android"
|
||||
* This includes keyboard selection message, usb connection messages, etc
|
||||
* Hope it does not filter out too much, we will see...
|
||||
*/
|
||||
|
||||
if (source.equals("android") ||
|
||||
source.equals("com.android.systemui") ||
|
||||
source.equals("com.android.dialer") ||
|
||||
source.equals("com.cyanogenmod.eleven")) {
|
||||
LOG.info("Not forwarding notification, is a system event");
|
||||
return;
|
||||
}
|
||||
|
||||
if (source.equals("com.moez.QKSMS") ||
|
||||
source.equals("com.android.mms") ||
|
||||
source.equals("com.sonyericsson.conversations") ||
|
||||
source.equals("com.android.messaging") ||
|
||||
source.equals("org.smssecure.smssecure")) {
|
||||
if (!"never".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (GBApplication.isBlacklisted(source)) {
|
||||
LOG.info("Not forwarding notification, application is blacklisted");
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationSpec notificationSpec = new NotificationSpec();
|
||||
notificationSpec.id = (int) sbn.getPostTime(); //FIMXE: a truly unique id would be better
|
||||
|
||||
// determinate Source App Name ("Label")
|
||||
PackageManager pm = getPackageManager();
|
||||
@ -266,10 +214,6 @@ public class NotificationListener extends NotificationListenerService {
|
||||
notificationSpec.type = AppNotificationType.getInstance().get(source);
|
||||
|
||||
if (source.startsWith("com.fsck.k9")) {
|
||||
// we dont want group summaries at all for k9
|
||||
if ((notification.flags & Notification.FLAG_GROUP_SUMMARY) == Notification.FLAG_GROUP_SUMMARY) {
|
||||
return;
|
||||
}
|
||||
preferBigText = true;
|
||||
}
|
||||
|
||||
@ -277,10 +221,9 @@ public class NotificationListener extends NotificationListenerService {
|
||||
notificationSpec.type = NotificationType.UNKNOWN;
|
||||
}
|
||||
|
||||
LOG.info("Processing notification from source " + source + " with flags: " + notification.flags);
|
||||
LOG.info("Processing notification " + notificationSpec.id + " from source " + source + " with flags: " + notification.flags);
|
||||
|
||||
dissectNotificationTo(notification, notificationSpec, preferBigText);
|
||||
notificationSpec.id = (int) sbn.getPostTime(); //FIMXE: a truly unique id would be better
|
||||
|
||||
// ignore Gadgetbridge's very own notifications, except for those from the debug screen
|
||||
if (getApplicationContext().getPackageName().equals(source)) {
|
||||
@ -292,11 +235,6 @@ public class NotificationListener extends NotificationListenerService {
|
||||
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(notification);
|
||||
List<NotificationCompat.Action> actions = wearableExtender.getActions();
|
||||
|
||||
if (actions.isEmpty() && (notification.flags & Notification.FLAG_GROUP_SUMMARY) == Notification.FLAG_GROUP_SUMMARY) { //this could cause #395 to come back
|
||||
LOG.info("Not forwarding notification, FLAG_GROUP_SUMMARY is set and no wearable action present. Notification flags: " + notification.flags);
|
||||
return;
|
||||
}
|
||||
|
||||
for (NotificationCompat.Action act : actions) {
|
||||
if (act != null && act.getRemoteInputs() != null) {
|
||||
LOG.info("found wearable action: " + act.getTitle() + " " + sbn.getTag());
|
||||
@ -306,11 +244,17 @@ public class NotificationListener extends NotificationListenerService {
|
||||
}
|
||||
}
|
||||
|
||||
if ((notificationSpec.flags & NotificationSpec.FLAG_WEARABLE_REPLY) == 0 && NotificationCompat.isGroupSummary(notification)) { //this could cause #395 to come back
|
||||
LOG.info("Not forwarding notification, FLAG_GROUP_SUMMARY is set and no wearable action present. Notification flags: " + notification.flags);
|
||||
return;
|
||||
}
|
||||
|
||||
GBApplication.deviceService().onNotification(notificationSpec);
|
||||
}
|
||||
|
||||
private void dissectNotificationTo(Notification notification, NotificationSpec notificationSpec, boolean preferBigText) {
|
||||
Bundle extras = notification.extras;
|
||||
|
||||
Bundle extras = NotificationCompat.getExtras(notification);
|
||||
|
||||
//dumpExtras(extras);
|
||||
|
||||
@ -321,9 +265,9 @@ public class NotificationListener extends NotificationListenerService {
|
||||
|
||||
CharSequence contentCS = null;
|
||||
if (preferBigText && extras.containsKey(Notification.EXTRA_BIG_TEXT)) {
|
||||
contentCS = extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
|
||||
contentCS = extras.getCharSequence(NotificationCompat.EXTRA_BIG_TEXT);
|
||||
} else if (extras.containsKey(Notification.EXTRA_TEXT)) {
|
||||
contentCS = extras.getCharSequence(Notification.EXTRA_TEXT);
|
||||
contentCS = extras.getCharSequence(NotificationCompat.EXTRA_TEXT);
|
||||
}
|
||||
if (contentCS != null) {
|
||||
notificationSpec.body = contentCS.toString();
|
||||
@ -344,31 +288,18 @@ public class NotificationListener extends NotificationListenerService {
|
||||
/**
|
||||
* Try to handle media session notifications that tell info about the current play state.
|
||||
*
|
||||
* @param notification The notification to handle.
|
||||
* @param mediaSession The mediasession to handle.
|
||||
* @return true if notification was handled, false otherwise
|
||||
*/
|
||||
public boolean handleMediaSessionNotification(Notification notification) {
|
||||
|
||||
// this code requires Android 5.0 or newer
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean handleMediaSessionNotification(MediaSessionCompat.Token mediaSession) {
|
||||
MusicSpec musicSpec = new MusicSpec();
|
||||
MusicStateSpec stateSpec = new MusicStateSpec();
|
||||
|
||||
Bundle extras = notification.extras;
|
||||
if (extras == null)
|
||||
return false;
|
||||
|
||||
if (extras.get(Notification.EXTRA_MEDIA_SESSION) == null)
|
||||
return false;
|
||||
|
||||
MediaController c;
|
||||
MediaControllerCompat c;
|
||||
try {
|
||||
c = new MediaController(getApplicationContext(), (MediaSession.Token) extras.get(Notification.EXTRA_MEDIA_SESSION));
|
||||
c = new MediaControllerCompat(getApplicationContext(), mediaSession);
|
||||
|
||||
PlaybackState s = c.getPlaybackState();
|
||||
PlaybackStateCompat s = c.getPlaybackState();
|
||||
stateSpec.position = (int) (s.getPosition() / 1000);
|
||||
stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed());
|
||||
stateSpec.repeat = 1;
|
||||
@ -388,57 +319,41 @@ public class NotificationListener extends NotificationListenerService {
|
||||
break;
|
||||
}
|
||||
|
||||
MediaMetadata d = c.getMetadata();
|
||||
MediaMetadataCompat d = c.getMetadata();
|
||||
if (d == null)
|
||||
return false;
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_ARTIST))
|
||||
musicSpec.artist = d.getString(MediaMetadata.METADATA_KEY_ARTIST);
|
||||
musicSpec.artist = d.getString(MediaMetadataCompat.METADATA_KEY_ARTIST);
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_ALBUM))
|
||||
musicSpec.album = d.getString(MediaMetadata.METADATA_KEY_ALBUM);
|
||||
musicSpec.album = d.getString(MediaMetadataCompat.METADATA_KEY_ALBUM);
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_TITLE))
|
||||
musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE);
|
||||
musicSpec.track = d.getString(MediaMetadataCompat.METADATA_KEY_TITLE);
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION))
|
||||
musicSpec.duration = (int) d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000;
|
||||
musicSpec.duration = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_DURATION) / 1000;
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS))
|
||||
musicSpec.trackCount = (int) d.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS);
|
||||
musicSpec.trackCount = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS);
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
|
||||
musicSpec.trackNr = (int) d.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
|
||||
musicSpec.trackNr = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER);
|
||||
|
||||
// finally, tell the device about it
|
||||
GBApplication.deviceService().onSetMusicInfo(musicSpec);
|
||||
GBApplication.deviceService().onSetMusicState(stateSpec);
|
||||
|
||||
return true;
|
||||
} catch (NullPointerException e) {
|
||||
} catch (NullPointerException | RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationRemoved(StatusBarNotification sbn) {
|
||||
//FIXME: deduplicate code
|
||||
if (!isServiceRunning() || sbn == null) {
|
||||
if (shouldIgnore(sbn))
|
||||
return;
|
||||
}
|
||||
|
||||
String source = sbn.getPackageName();
|
||||
Notification notification = sbn.getNotification();
|
||||
if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source.equals("android") ||
|
||||
source.equals("com.android.systemui") ||
|
||||
source.equals("com.android.dialer") ||
|
||||
source.equals("com.cyanogenmod.eleven")) {
|
||||
return;
|
||||
}
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (prefs.getBoolean("autoremove_notifications", false)) {
|
||||
LOG.info("notification removed, will ask device to delete it");
|
||||
|
||||
GBApplication.deviceService().onDeleteNotification((int) sbn.getPostTime()); //FIMXE: a truly unique id would be better
|
||||
GBApplication.deviceService().onDeleteNotification(sbn.getPackageName().hashCode() * 31 + sbn.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -451,4 +366,88 @@ public class NotificationListener extends NotificationListenerService {
|
||||
LOG.debug(String.format("Notification extra: %s %s (%s)", key, value.toString(), value.getClass().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldIgnore(StatusBarNotification sbn) {
|
||||
/*
|
||||
* return early if DeviceCommunicationService is not running,
|
||||
* else the service would get started every time we get a notification.
|
||||
* unfortunately we cannot enable/disable NotificationListener at runtime like we do with
|
||||
* broadcast receivers because it seems to invalidate the permissions that are
|
||||
* necessary for NotificationListenerService
|
||||
*/
|
||||
if (!isServiceRunning() || sbn == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (shouldIgnoreSource(sbn.getPackageName()))
|
||||
return true;
|
||||
|
||||
if (shouldIgnoreNotification(sbn.getNotification()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean shouldIgnoreSource(String source) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
|
||||
/* do not display messages from "android"
|
||||
* This includes keyboard selection message, usb connection messages, etc
|
||||
* Hope it does not filter out too much, we will see...
|
||||
*/
|
||||
|
||||
if (source.equals("android") ||
|
||||
source.equals("com.android.systemui") ||
|
||||
source.equals("com.android.dialer") ||
|
||||
source.equals("com.cyanogenmod.eleven")) {
|
||||
LOG.info("Ignoring notification, is a system event");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (source.equals("com.moez.QKSMS") ||
|
||||
source.equals("com.android.mms") ||
|
||||
source.equals("com.sonyericsson.conversations") ||
|
||||
source.equals("com.android.messaging") ||
|
||||
source.equals("org.smssecure.smssecure")) {
|
||||
if (!"never".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (GBApplication.appIsBlacklisted(source)) {
|
||||
LOG.info("Ignoring notification, application is blacklisted");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean shouldIgnoreNotification(Notification notification) {
|
||||
|
||||
MediaSessionCompat.Token mediaSession = NotificationCompat.getMediaSession(notification);
|
||||
//try to handle media session notifications
|
||||
if (mediaSession != null && handleMediaSessionNotification(mediaSession))
|
||||
return true;
|
||||
|
||||
//ignore notifications marked as LocalOnly https://developer.android.com/reference/android/app/Notification.html#FLAG_LOCAL_ONLY
|
||||
if (NotificationCompat.getLocalOnly(notification))
|
||||
return true;
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
|
||||
PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
if (powermanager.isScreenOn()) {
|
||||
// LOG.info("Not forwarding notification, screen seems to be on and settings do not allow this");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) {
|
||||
// LOG.info("Not forwarding notification, FLAG_ONGOING_EVENT is set. Notification flags: " + notification.flags);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.CalendarContract.Instances;
|
||||
import android.text.format.Time;
|
||||
import android.util.Log;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -34,6 +33,8 @@ import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
|
||||
public class CalendarEvents {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CalendarEvents.class);
|
||||
|
||||
@ -102,7 +103,11 @@ public class CalendarEvents {
|
||||
evtCursor.getString(7),
|
||||
!evtCursor.getString(8).equals("0")
|
||||
);
|
||||
calendarEventList.add(calEvent);
|
||||
if (!GBApplication.calendarIsBlacklisted(calEvent.getCalName())) {
|
||||
calendarEventList.add(calEvent);
|
||||
} else {
|
||||
LOG.debug("calendar " + calEvent.getCalName() + " skipped because it's blacklisted");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -28,15 +28,16 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
* and may not be changed.
|
||||
*/
|
||||
public enum DeviceType {
|
||||
UNKNOWN(-1, R.drawable.ic_launcher, R.drawable.ic_device_default_disabled),
|
||||
UNKNOWN(-1, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled),
|
||||
PEBBLE(1, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled),
|
||||
MIBAND(10, R.drawable.ic_device_miband, R.drawable.ic_device_miband_disabled),
|
||||
MIBAND2(11, R.drawable.ic_device_miband, R.drawable.ic_device_miband_disabled),
|
||||
AMAZFITBIP(12, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
|
||||
VIBRATISSIMO(20, R.drawable.ic_device_lovetoy, R.drawable.ic_device_lovetoy_disabled),
|
||||
LIVEVIEW(30, R.drawable.ic_launcher, R.drawable.ic_device_default_disabled),
|
||||
LIVEVIEW(30, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled),
|
||||
HPLUS(40, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
|
||||
MAKIBESF68(41, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
|
||||
TEST(1000, R.drawable.ic_launcher, R.drawable.ic_device_default_disabled);
|
||||
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled);
|
||||
|
||||
private final int key;
|
||||
@DrawableRes
|
||||
|
@ -18,6 +18,7 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
@ -27,9 +28,11 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -584,12 +587,14 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
|
||||
if (enable && initialized && coordinator != null && coordinator.supportsCalendarEvents()) {
|
||||
if (mCalendarReceiver == null && getPrefs().getBoolean("enable_calendar_sync", true)) {
|
||||
IntentFilter calendarIntentFilter = new IntentFilter();
|
||||
calendarIntentFilter.addAction("android.intent.action.PROVIDER_CHANGED");
|
||||
calendarIntentFilter.addDataScheme("content");
|
||||
calendarIntentFilter.addDataAuthority("com.android.calendar", null);
|
||||
mCalendarReceiver = new CalendarReceiver(mGBDevice);
|
||||
registerReceiver(mCalendarReceiver, calendarIntentFilter);
|
||||
if (!(GBApplication.isRunningMarshmallowOrLater() && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)) {
|
||||
IntentFilter calendarIntentFilter = new IntentFilter();
|
||||
calendarIntentFilter.addAction("android.intent.action.PROVIDER_CHANGED");
|
||||
calendarIntentFilter.addDataScheme("content");
|
||||
calendarIntentFilter.addDataAuthority("com.android.calendar", null);
|
||||
mCalendarReceiver = new CalendarReceiver(mGBDevice);
|
||||
registerReceiver(mCalendarReceiver, calendarIntentFilter);
|
||||
}
|
||||
}
|
||||
if (mAlarmReceiver == null) {
|
||||
mAlarmReceiver = new AlarmReceiver();
|
||||
|
@ -30,6 +30,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.AmazfitBipSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport;
|
||||
@ -109,6 +110,9 @@ public class DeviceSupportFactory {
|
||||
case MIBAND2:
|
||||
deviceSupport = new ServiceDeviceSupport(new MiBand2Support(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case AMAZFITBIP:
|
||||
deviceSupport = new ServiceDeviceSupport(new AmazfitBipSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case VIBRATISSIMO:
|
||||
deviceSupport = new ServiceDeviceSupport(new VibratissimoSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
|
@ -0,0 +1,202 @@
|
||||
/* Copyright (C) 2016-2017 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.service.btclassic;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.Context;
|
||||
import android.os.ParcelUuid;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public abstract class BtClassicIoThread extends GBDeviceIoThread {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BtClassicIoThread.class);
|
||||
|
||||
private final GBDeviceProtocol mProtocol;
|
||||
private final AbstractSerialDeviceSupport mDeviceSupport;
|
||||
|
||||
|
||||
private BluetoothAdapter mBtAdapter = null;
|
||||
private BluetoothSocket mBtSocket = null;
|
||||
private InputStream mInStream = null;
|
||||
private OutputStream mOutStream = null;
|
||||
private boolean mQuit = false;
|
||||
|
||||
@Override
|
||||
public void quit() {
|
||||
mQuit = true;
|
||||
if (mBtSocket != null) {
|
||||
try {
|
||||
mBtSocket.close();
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean mIsConnected = false;
|
||||
|
||||
|
||||
public BtClassicIoThread(GBDevice gbDevice, Context context, GBDeviceProtocol deviceProtocol, AbstractSerialDeviceSupport deviceSupport, BluetoothAdapter btAdapter) {
|
||||
super(gbDevice, context);
|
||||
mProtocol = deviceProtocol;
|
||||
mDeviceSupport = deviceSupport;
|
||||
mBtAdapter = btAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] bytes) {
|
||||
if (null == bytes)
|
||||
return;
|
||||
LOG.debug("writing:" + GB.hexdump(bytes, 0, bytes.length));
|
||||
try {
|
||||
mOutStream.write(bytes);
|
||||
mOutStream.flush();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error writing.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mIsConnected = connect();
|
||||
if (!mIsConnected) {
|
||||
setUpdateState(GBDevice.State.NOT_CONNECTED);
|
||||
return;
|
||||
}
|
||||
mQuit = false;
|
||||
|
||||
while (!mQuit) {
|
||||
LOG.info("Ready for a new message exchange.");
|
||||
|
||||
try {
|
||||
GBDeviceEvent deviceEvents[] = mProtocol.decodeResponse(parseIncoming(mInStream));
|
||||
if (deviceEvents == null) {
|
||||
LOG.info("unhandled message");
|
||||
} else {
|
||||
for (GBDeviceEvent deviceEvent : deviceEvents) {
|
||||
if (deviceEvent == null) {
|
||||
continue;
|
||||
}
|
||||
mDeviceSupport.evaluateGBDeviceEvent(deviceEvent);
|
||||
}
|
||||
}
|
||||
} catch (SocketTimeoutException ignore) {
|
||||
LOG.debug("socket timeout, we can't help but ignore this");
|
||||
} catch (IOException e) {
|
||||
LOG.info(e.getMessage());
|
||||
mIsConnected = false;
|
||||
mBtSocket = null;
|
||||
mInStream = null;
|
||||
mOutStream = null;
|
||||
LOG.info("Bluetooth socket closed, will quit IO Thread");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mIsConnected = false;
|
||||
if (mBtSocket != null) {
|
||||
try {
|
||||
mBtSocket.close();
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
mBtSocket = null;
|
||||
}
|
||||
setUpdateState(GBDevice.State.NOT_CONNECTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean connect() {
|
||||
GBDevice.State originalState = gbDevice.getState();
|
||||
setUpdateState(GBDevice.State.CONNECTING);
|
||||
|
||||
try {
|
||||
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(gbDevice.getAddress());
|
||||
ParcelUuid uuids[] = btDevice.getUuids();
|
||||
if (uuids == null) {
|
||||
LOG.warn("Device provided no UUIDs to connect to, giving up: " + gbDevice);
|
||||
return false;
|
||||
}
|
||||
for (ParcelUuid uuid : uuids) {
|
||||
LOG.info("found service UUID " + uuid);
|
||||
}
|
||||
mBtSocket = btDevice.createRfcommSocketToServiceRecord(getUuidToConnect(uuids));
|
||||
mBtSocket.connect();
|
||||
mInStream = mBtSocket.getInputStream();
|
||||
mOutStream = mBtSocket.getOutputStream();
|
||||
setUpdateState(GBDevice.State.CONNECTED);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Server socket cannot be started.");
|
||||
//LOG.error(e.getMessage());
|
||||
setUpdateState(originalState);
|
||||
mInStream = null;
|
||||
mOutStream = null;
|
||||
mBtSocket = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
write(mProtocol.encodeSetTime());
|
||||
setUpdateState(GBDevice.State.INITIALIZED);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the uuid to connect to.
|
||||
* Default implementation returns the first of the given uuids that were
|
||||
* read from the remote device.
|
||||
* @param uuids
|
||||
* @return
|
||||
*/
|
||||
@NonNull
|
||||
protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
|
||||
return uuids[0].getUuid();
|
||||
}
|
||||
|
||||
protected void setUpdateState(GBDevice.State state) {
|
||||
gbDevice.setState(state);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an incoming message for consuming by the GBDeviceProtocol
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @param inStream
|
||||
*/
|
||||
protected abstract byte[] parseIncoming(InputStream inStream) throws IOException;
|
||||
}
|
@ -1,3 +1,19 @@
|
||||
/* Copyright (C) 2015-2017 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/>. */
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer, Uwe Hermann
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -37,7 +37,8 @@ public enum AlertCategory {
|
||||
// 10-250 reserved for future use
|
||||
// 251-255 defined by service specification
|
||||
Any(255),
|
||||
Custom(-1);
|
||||
Custom(-1),
|
||||
CustomMiBand2(-6);
|
||||
|
||||
private final int id;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -33,12 +33,16 @@ import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public class AlertNotificationProfile<T extends AbstractBTLEDeviceSupport> extends AbstractBleProfile<T> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AlertNotificationProfile.class);
|
||||
private static final int MAX_MSG_LENGTH = 18;
|
||||
private int maxLength = 18; // Mi2-ism?
|
||||
|
||||
public AlertNotificationProfile(T support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
public void setMaxLength(int maxLength) {
|
||||
this.maxLength = maxLength;
|
||||
}
|
||||
|
||||
public void configure(TransactionBuilder builder, AlertNotificationControl control) {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_NOTIFICATION_CONTROL_POINT);
|
||||
if (characteristic != null) {
|
||||
@ -57,21 +61,21 @@ public class AlertNotificationProfile<T extends AbstractBTLEDeviceSupport> exten
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_NEW_ALERT);
|
||||
if (characteristic != null) {
|
||||
String message = StringUtils.ensureNotNull(alert.getMessage());
|
||||
if (message.length() > MAX_MSG_LENGTH && strategy == OverflowStrategy.TRUNCATE) {
|
||||
message = StringUtils.truncate(message, MAX_MSG_LENGTH);
|
||||
if (message.length() > maxLength && strategy == OverflowStrategy.TRUNCATE) {
|
||||
message = StringUtils.truncate(message, maxLength);
|
||||
}
|
||||
|
||||
int numChunks = message.length() / MAX_MSG_LENGTH;
|
||||
if (message.length() % MAX_MSG_LENGTH > 0) {
|
||||
int numChunks = message.length() / maxLength;
|
||||
if (message.length() % maxLength > 0) {
|
||||
numChunks++;
|
||||
}
|
||||
|
||||
try {
|
||||
boolean hasAlerted = false;
|
||||
for (int i = 0; i < numChunks; i++) {
|
||||
int offset = i * MAX_MSG_LENGTH;
|
||||
int offset = i * maxLength;
|
||||
int restLength = message.length() - offset;
|
||||
message = message.substring(offset, offset + Math.min(MAX_MSG_LENGTH, restLength));
|
||||
message = message.substring(offset, offset + Math.min(maxLength, restLength));
|
||||
if (hasAlerted && message.length() == 0) {
|
||||
// no need to do it again when there is no text content
|
||||
break;
|
||||
@ -91,10 +95,17 @@ public class AlertNotificationProfile<T extends AbstractBTLEDeviceSupport> exten
|
||||
}
|
||||
}
|
||||
|
||||
public void newAlert(TransactionBuilder builder, NewAlert alert) {
|
||||
newAlert(builder, alert, OverflowStrategy.TRUNCATE);
|
||||
}
|
||||
|
||||
protected byte[] getAlertMessage(NewAlert alert, String message, int chunk) throws IOException {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream(100);
|
||||
stream.write(BLETypeConversions.fromUint8(alert.getCategory().getId()));
|
||||
stream.write(BLETypeConversions.fromUint8(alert.getNumAlerts()));
|
||||
if (alert.getCategory() == AlertCategory.CustomMiBand2) {
|
||||
stream.write(BLETypeConversions.fromUint8(alert.getCustomIcon()));
|
||||
}
|
||||
|
||||
if (message.length() > 0) {
|
||||
stream.write(BLETypeConversions.toUtf8s(message));
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification;
|
||||
|
||||
import android.icu.util.IslamicCalendar;
|
||||
|
||||
/**
|
||||
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.new_alert.xml&u=org.bluetooth.characteristic.new_alert.xml
|
||||
*
|
||||
@ -47,6 +49,7 @@ public class NewAlert {
|
||||
private final AlertCategory category;
|
||||
private final int numAlerts;
|
||||
private final String message;
|
||||
private int customIcon = -1;
|
||||
|
||||
public NewAlert(AlertCategory category, int /*uint8*/ numAlerts, String /*utf8s*/ message) {
|
||||
this.category = category;
|
||||
@ -54,6 +57,13 @@ public class NewAlert {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public NewAlert(AlertCategory category, int /*uint8*/ numAlerts, String /*utf8s*/ message, int customIcon) {
|
||||
this.category = category;
|
||||
this.numAlerts = numAlerts;
|
||||
this.message = message;
|
||||
this.customIcon = customIcon;
|
||||
}
|
||||
|
||||
public AlertCategory getCategory() {
|
||||
return category;
|
||||
}
|
||||
@ -65,4 +75,8 @@ public class NewAlert {
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public int getCustomIcon() {
|
||||
return customIcon;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,80 @@
|
||||
/* Copyright (C) 2017 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.service.devices.amazfitbip;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.FirmwareType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.Mi2FirmwareInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
||||
|
||||
public class AmazfitBipFirmwareInfo extends Mi2FirmwareInfo {
|
||||
// total crap maybe
|
||||
private static final byte[] GPS_HEADER = new byte[]{
|
||||
(byte) 0xcb, 0x51, (byte) 0xc1, 0x30, 0x41, (byte) 0x9e, 0x5e, (byte) 0xd3,
|
||||
0x51, 0x35, (byte) 0xdf, 0x66, (byte) 0xed, (byte) 0xd9, 0x5f, (byte) 0xa7
|
||||
};
|
||||
|
||||
// guessed - at least it is the same accross current versions and different from other devices
|
||||
private static final byte[] FW_HEADER = new byte[]{
|
||||
0x68, 0x46, 0x70, 0x47, 0x68, 0x46, 0x70, 0x47,
|
||||
0x68, 0x46, 0x70, 0x47, 0x68, 0x46, 0x70, 0x47
|
||||
};
|
||||
|
||||
private static final int FW_HEADER_OFFSET = 0x9330;
|
||||
|
||||
private static final byte[] RES_HEADER = new byte[]{ // HMRES resources file (*.res)
|
||||
0x48, 0x4d, 0x52, 0x45, 0x53
|
||||
};
|
||||
|
||||
|
||||
static {
|
||||
// firmware
|
||||
crcToVersion.put(25257, "0.0.8.74");
|
||||
|
||||
// resources
|
||||
crcToVersion.put(12586, "RES 0.0.8.74");
|
||||
|
||||
// gps
|
||||
crcToVersion.put(61520, "GPS 0.0.8.xx");
|
||||
}
|
||||
|
||||
public AmazfitBipFirmwareInfo(byte[] bytes) {
|
||||
super(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FirmwareType determineFirmwareType(byte[] bytes) {
|
||||
if (ArrayUtils.startsWith(bytes, RES_HEADER)) {
|
||||
return FirmwareType.RES;
|
||||
}
|
||||
if (ArrayUtils.startsWith(bytes, GPS_HEADER)) {
|
||||
return FirmwareType.GPS;
|
||||
}
|
||||
if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET)) {
|
||||
// TODO: this is certainly not a correct validation, but it works for now
|
||||
return FirmwareType.FIRMWARE;
|
||||
}
|
||||
return FirmwareType.INVALID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGenerallyCompatibleWith(GBDevice device) {
|
||||
return isHeaderValid() && device.getType() == DeviceType.AMAZFITBIP;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
/* Copyright (C) 2017 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.service.devices.amazfitbip;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.SimpleTimeZone;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipIcon;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipWeatherConditions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.operations.AmazfitBipUpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
||||
|
||||
public class AmazfitBipSupport extends MiBand2Support {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipSupport.class);
|
||||
|
||||
@Override
|
||||
public NotificationStrategy getNotificationStrategy() {
|
||||
return new AmazfitBipTextNotificationStrategy(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotification(NotificationSpec notificationSpec) {
|
||||
if (notificationSpec.type == NotificationType.GENERIC_ALARM_CLOCK) {
|
||||
onAlarmClock(notificationSpec);
|
||||
return;
|
||||
}
|
||||
|
||||
String senderOrTiltle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
|
||||
|
||||
String message = StringUtils.truncate(senderOrTiltle, 32) + "\0";
|
||||
if (notificationSpec.subject != null) {
|
||||
message += StringUtils.truncate(notificationSpec.subject, 128) + "\n\n";
|
||||
}
|
||||
if (notificationSpec.body != null) {
|
||||
message += StringUtils.truncate(notificationSpec.body, 128);
|
||||
}
|
||||
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("new notification");
|
||||
AlertNotificationProfile<?> profile = new AlertNotificationProfile(this);
|
||||
profile.setMaxLength(255); // TODO: find out real limit, certainly it is more than 18 which is default
|
||||
|
||||
int customIconId = AmazfitBipIcon.mapToIconId(notificationSpec.type);
|
||||
|
||||
AlertCategory alertCategory = AlertCategory.CustomMiBand2;
|
||||
|
||||
// The SMS icon for AlertCategory.SMS is unique and not available as iconId
|
||||
if (notificationSpec.type == NotificationType.GENERIC_SMS) {
|
||||
alertCategory = AlertCategory.SMS;
|
||||
}
|
||||
|
||||
NewAlert alert = new NewAlert(alertCategory, 1, message, customIconId);
|
||||
profile.newAlert(builder, alert);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to send notification to Amazfit Bip", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean start) {
|
||||
CallSpec callSpec = new CallSpec();
|
||||
callSpec.command = start ? CallSpec.CALL_INCOMING : CallSpec.CALL_END;
|
||||
callSpec.name = "Gadgetbridge";
|
||||
onSetCallState(callSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleButtonPressed(byte[] value) {
|
||||
if (value == null || value.length != 1) {
|
||||
return;
|
||||
}
|
||||
GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
|
||||
|
||||
if (value[0] == 0x07) {
|
||||
callCmd.event = GBDeviceEventCallControl.Event.REJECT;
|
||||
} else if (value[0] == 0x09) {
|
||||
callCmd.event = GBDeviceEventCallControl.Event.ACCEPT;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
evaluateGBDeviceEvent(callCmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInstallApp(Uri uri) {
|
||||
try {
|
||||
new AmazfitBipUpdateFirmwareOperation(uri, this).perform();
|
||||
} catch (IOException ex) {
|
||||
GB.toast(getContext(), "Firmware cannot be installed: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("Sending weather forecast");
|
||||
Version version = new Version(gbDevice.getFirmwareVersion());
|
||||
|
||||
boolean supportsConditionString = false;
|
||||
if (version.compareTo(new Version("0.0.8.74")) >= 0) {
|
||||
supportsConditionString = true;
|
||||
}
|
||||
|
||||
final byte NR_DAYS = 2;
|
||||
int bytesPerDay = 4;
|
||||
int conditionsLength = 0;
|
||||
if (supportsConditionString) {
|
||||
bytesPerDay = 5;
|
||||
conditionsLength = weatherSpec.currentCondition.getBytes().length;
|
||||
}
|
||||
int length = 7 + bytesPerDay * NR_DAYS + conditionsLength;
|
||||
ByteBuffer buf = ByteBuffer.allocate(length);
|
||||
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
buf.put((byte) 1);
|
||||
buf.putInt(weatherSpec.timestamp);
|
||||
int tz_offset_hours = SimpleTimeZone.getDefault().getOffset(weatherSpec.timestamp * 1000L) / (1000 * 60 * 60);
|
||||
buf.put((byte) (tz_offset_hours * 4));
|
||||
|
||||
buf.put(NR_DAYS);
|
||||
|
||||
byte condition = AmazfitBipWeatherConditions.mapToAmazfitBipWeatherCode(weatherSpec.currentConditionCode);
|
||||
buf.put(condition);
|
||||
buf.put(condition);
|
||||
buf.put((byte) (weatherSpec.todayMaxTemp - 273));
|
||||
buf.put((byte) (weatherSpec.todayMinTemp - 273));
|
||||
if (supportsConditionString) {
|
||||
buf.put(weatherSpec.currentCondition.getBytes());
|
||||
buf.put((byte) 0); //
|
||||
}
|
||||
condition = AmazfitBipWeatherConditions.mapToAmazfitBipWeatherCode(weatherSpec.tomorrowConditionCode);
|
||||
|
||||
buf.put(condition);
|
||||
buf.put(condition);
|
||||
buf.put((byte) (weatherSpec.tomorrowMaxTemp - 273));
|
||||
buf.put((byte) (weatherSpec.tomorrowMinTemp - 273));
|
||||
if (supportsConditionString) {
|
||||
buf.put((byte) 0); // not yet in weatherspec
|
||||
}
|
||||
|
||||
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/* Copyright (C) 2017 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.service.devices.amazfitbip;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.OverflowStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.Mi2TextNotificationStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
|
||||
// This class in no longer in use except for incoming calls
|
||||
class AmazfitBipTextNotificationStrategy extends Mi2TextNotificationStrategy {
|
||||
|
||||
AmazfitBipTextNotificationStrategy(MiBand2Support support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, BtLEAction extraAction, TransactionBuilder builder) {
|
||||
if (simpleNotification != null && !StringUtils.isEmpty(simpleNotification.getMessage())) {
|
||||
sendAlert(simpleNotification, builder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendAlert(@NonNull SimpleNotification simpleNotification, TransactionBuilder builder) {
|
||||
AlertNotificationProfile<?> profile = new AlertNotificationProfile<>(getSupport());
|
||||
profile.setMaxLength(255); // TODO: find out real limit, certainly it is more than 18 which is default
|
||||
|
||||
AlertCategory category = simpleNotification.getAlertCategory();
|
||||
switch (simpleNotification.getAlertCategory()) {
|
||||
// only these are confirmed working so far on Amazfit Bip
|
||||
case Email:
|
||||
case IncomingCall:
|
||||
case SMS:
|
||||
break;
|
||||
// default to SMS for non working categories
|
||||
default:
|
||||
category = AlertCategory.SMS;
|
||||
}
|
||||
NewAlert alert = new NewAlert(category, 1, simpleNotification.getMessage());
|
||||
profile.newAlert(builder, alert, OverflowStrategy.TRUNCATE);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/* Copyright (C) 2017 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.service.devices.amazfitbip.operations;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.AmazfitBipSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class AmazfitBipUpdateFirmwareOperation extends UpdateFirmwareOperation {
|
||||
public AmazfitBipUpdateFirmwareOperation(Uri uri, AmazfitBipSupport support) {
|
||||
super(uri, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPerform() throws IOException {
|
||||
AmazfitBipFWHelper mFwHelper = new AmazfitBipFWHelper(uri, getContext());
|
||||
|
||||
firmwareInfo = mFwHelper.getFirmwareInfo();
|
||||
if (!firmwareInfo.isGenerallyCompatibleWith(getDevice())) {
|
||||
throw new IOException("Firmware is not compatible with the given device: " + getDevice().getAddress());
|
||||
}
|
||||
|
||||
if (!sendFwInfo()) {
|
||||
displayMessage(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR);
|
||||
done();
|
||||
}
|
||||
//the firmware will be sent by the notification listener if the band confirms that the metadata are ok.
|
||||
}
|
||||
}
|
@ -39,23 +39,23 @@ public class HPlusDataRecordDaySlot extends HPlusDataRecord {
|
||||
/**
|
||||
* Number of steps
|
||||
*/
|
||||
public int steps;
|
||||
public int steps = ActivitySample.NOT_MEASURED;
|
||||
|
||||
/**
|
||||
* Number of seconds without activity (TBC)
|
||||
*/
|
||||
public int secondsInactive;
|
||||
public int secondsInactive = ActivitySample.NOT_MEASURED;
|
||||
|
||||
/**
|
||||
* Average Heart Rate in Beats Per Minute
|
||||
*/
|
||||
public int heartRate;
|
||||
public int heartRate = ActivitySample.NOT_MEASURED;
|
||||
|
||||
private int age = 0;
|
||||
/**
|
||||
* Raw intensity calculated from calories
|
||||
*/
|
||||
public int intensity;
|
||||
public int intensity = ActivitySample.NOT_MEASURED;
|
||||
|
||||
public HPlusDataRecordDaySlot(byte[] data, int age) {
|
||||
super(data, TYPE_DAY_SLOT);
|
||||
@ -85,6 +85,8 @@ public class HPlusDataRecordDaySlot extends HPlusDataRecord {
|
||||
timestamp = (int) (slotTime.getTimeInMillis() / 1000L);
|
||||
|
||||
this.age = age;
|
||||
|
||||
intensity = (int) ((100*heartRate)/(208-0.7*age));
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
|
@ -24,6 +24,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
@ -80,19 +81,24 @@ class HPlusDataRecordRealtime extends HPlusDataRecord {
|
||||
int y = (data[8] & 0xFF) * 256 + (data[7] & 0xFF);
|
||||
|
||||
battery = data[9];
|
||||
|
||||
calories = x + y; // KCal
|
||||
|
||||
heartRate = data[11] & 0xFF; // BPM
|
||||
activeTime = (data[14] & 0xFF * 256) + (data[13] & 0xFF);
|
||||
if(heartRate == 255) {
|
||||
intensity = 0;
|
||||
activityKind = ActivityKind.TYPE_NOT_MEASURED;
|
||||
|
||||
if (battery == 255) {
|
||||
battery = ActivitySample.NOT_MEASURED;
|
||||
heartRate = ActivitySample.NOT_MEASURED;
|
||||
}
|
||||
else {
|
||||
intensity = (int) ((100*heartRate)/(208-0.7*age));
|
||||
activityKind = ActivityKind.TYPE_UNKNOWN;
|
||||
intensity = 0;
|
||||
activityKind = ActivityKind.TYPE_NOT_WORN;
|
||||
} else {
|
||||
heartRate = data[11] & 0xFF; // BPM
|
||||
if (heartRate == 255) {
|
||||
intensity = 0;
|
||||
activityKind = ActivityKind.TYPE_NOT_MEASURED;
|
||||
heartRate = ActivitySample.NOT_MEASURED;
|
||||
} else {
|
||||
intensity = (int) ((100 * heartRate) / (208 - 0.7 * age));
|
||||
activityKind = HPlusDataRecord.TYPE_REALTIME;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusHealthSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlay;
|
||||
@ -123,7 +124,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
break;
|
||||
}
|
||||
|
||||
if(gbDevice.getState() == GBDevice.State.NOT_CONNECTED){
|
||||
if (gbDevice.getState() == GBDevice.State.NOT_CONNECTED) {
|
||||
quit();
|
||||
}
|
||||
|
||||
@ -137,11 +138,11 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
requestNextSleepData();
|
||||
}
|
||||
|
||||
if(now.compareTo(mGetDaySummaryTime) > 0) {
|
||||
if (now.compareTo(mGetDaySummaryTime) > 0) {
|
||||
requestDaySummaryData();
|
||||
}
|
||||
|
||||
if(now.compareTo(mHelloTime) > 0){
|
||||
if (now.compareTo(mHelloTime) > 0) {
|
||||
sendHello();
|
||||
}
|
||||
|
||||
@ -178,7 +179,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
mDaySlotRecords.clear();
|
||||
|
||||
try {
|
||||
if(!mHPlusSupport.isConnected())
|
||||
if (!mHPlusSupport.isConnected())
|
||||
mHPlusSupport.connect();
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder("startSyncDayStats");
|
||||
@ -188,7 +189,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_CURR_DATA});
|
||||
|
||||
mHPlusSupport.performConnected(builder.getTransaction());
|
||||
}catch(Exception e){
|
||||
} catch(Exception e) {
|
||||
LOG.warn("HPlus: Synchronization exception: " + e);
|
||||
}
|
||||
|
||||
@ -197,13 +198,13 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
}
|
||||
}
|
||||
|
||||
public void sendHello(){
|
||||
public void sendHello() {
|
||||
try {
|
||||
TransactionBuilder builder = new TransactionBuilder("hello");
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, HPlusConstants.CMD_ACTION_HELLO);
|
||||
mHPlusSupport.performConnected(builder.getTransaction());
|
||||
|
||||
}catch(Exception e){
|
||||
} catch(Exception e) {
|
||||
|
||||
}
|
||||
mHelloTime = GregorianCalendar.getInstance();
|
||||
@ -226,31 +227,30 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
|
||||
try{
|
||||
record = new HPlusDataRecordDaySlot(data, age);
|
||||
} catch(IllegalArgumentException e){
|
||||
} catch(IllegalArgumentException e) {
|
||||
LOG.info((e.getMessage()));
|
||||
return false;
|
||||
}
|
||||
|
||||
Calendar now = GregorianCalendar.getInstance();
|
||||
int nowSlot = now.get(Calendar.HOUR_OF_DAY) * 6 + (now.get(Calendar.MINUTE) / 10);
|
||||
if(record.slot == nowSlot){
|
||||
if(mCurrentDaySlot != null && mCurrentDaySlot != record){
|
||||
if (record.slot == nowSlot){
|
||||
if (mCurrentDaySlot != null && mCurrentDaySlot != record) {
|
||||
mCurrentDaySlot.accumulate(record);
|
||||
mDaySlotRecords.add(mCurrentDaySlot);
|
||||
mCurrentDaySlot = null;
|
||||
}else{
|
||||
} else {
|
||||
//Store it to a temp variable as this is an intermediate value
|
||||
mCurrentDaySlot = record;
|
||||
if(!mSlotsInitialSync)
|
||||
if (!mSlotsInitialSync)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(mSlotsInitialSync) {
|
||||
|
||||
if (mSlotsInitialSync) {
|
||||
//If the slot is in the future, actually it is from the previous day
|
||||
//Subtract a day of seconds
|
||||
if(record.slot > nowSlot){
|
||||
if (record.slot > nowSlot) {
|
||||
record.timestamp -= 3600 * 24;
|
||||
}
|
||||
|
||||
@ -259,7 +259,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
}
|
||||
|
||||
//Ignore the current slot as it is incomplete
|
||||
if(record.slot != nowSlot)
|
||||
if (record.slot != nowSlot)
|
||||
mDaySlotRecords.add(record);
|
||||
|
||||
//Still fetching ring buffer. Request the next slots
|
||||
@ -271,14 +271,14 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
}
|
||||
|
||||
//Keep buffering
|
||||
if(record.slot != 143)
|
||||
if (record.slot != 143)
|
||||
return true;
|
||||
} else {
|
||||
mGetDaySlotsTime = GregorianCalendar.getInstance();
|
||||
mGetDaySlotsTime.add(Calendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
|
||||
if(mDaySlotRecords.size() > 0) {
|
||||
if (mDaySlotRecords.size() > 0) {
|
||||
//Sort the samples
|
||||
Collections.sort(mDaySlotRecords, new Comparator<HPlusDataRecordDaySlot>() {
|
||||
public int compare(HPlusDataRecordDaySlot one, HPlusDataRecordDaySlot other) {
|
||||
@ -286,6 +286,8 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
}
|
||||
});
|
||||
|
||||
List<Integer> notWornSlots = new ArrayList<>();
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
List<HPlusHealthActivitySample> samples = new ArrayList<>();
|
||||
@ -293,23 +295,62 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
for (HPlusDataRecordDaySlot storedRecord : mDaySlotRecords) {
|
||||
|
||||
//Invalid records (no data) will be ignored
|
||||
if(!storedRecord.isValid())
|
||||
if (!storedRecord.isValid())
|
||||
continue;
|
||||
|
||||
HPlusHealthActivitySample sample = createSample(dbHandler, storedRecord.timestamp);
|
||||
|
||||
sample.setRawHPlusHealthData(storedRecord.getRawData());
|
||||
sample.setSteps(storedRecord.steps);
|
||||
|
||||
sample.setRawIntensity(storedRecord.intensity);
|
||||
sample.setHeartRate(storedRecord.heartRate);
|
||||
sample.setRawKind(storedRecord.type);
|
||||
sample.setRawIntensity(record.intensity);
|
||||
sample.setProvider(provider);
|
||||
samples.add(sample);
|
||||
|
||||
if (HPlusCoordinator.getAllDayHR(gbDevice.getAddress()) == HPlusConstants.ARG_HEARTRATE_ALLDAY_ON && storedRecord.heartRate == ActivitySample.NOT_MEASURED && storedRecord.steps <= 0) {
|
||||
notWornSlots.add(sample.getTimestamp());
|
||||
notWornSlots.add(sample.getTimestamp() + 10 * 60);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
provider.getSampleDao().insertOrReplaceInTx(samples);
|
||||
mDaySlotRecords.clear();
|
||||
|
||||
//Create an overlay with unused slots
|
||||
if (notWornSlots.size() > 0) {
|
||||
DaoSession session = dbHandler.getDaoSession();
|
||||
Long userId = DBHelper.getUser(session).getId();
|
||||
Long deviceId = DBHelper.getDevice(getDevice(), session).getId();
|
||||
|
||||
HPlusHealthActivityOverlayDao overlayDao = session.getHPlusHealthActivityOverlayDao();
|
||||
List<HPlusHealthActivityOverlay> overlayList = new ArrayList<>();
|
||||
|
||||
|
||||
int firstSlotTimestamp = notWornSlots.get(0);
|
||||
int lastSlotTimestamp = notWornSlots.get(0);
|
||||
|
||||
int i = 1;
|
||||
for (Integer timestamp : notWornSlots) {
|
||||
|
||||
//If it is the last of the samples or of the interruption period
|
||||
if (timestamp - lastSlotTimestamp > 10 * 60) {
|
||||
overlayList.add(new HPlusHealthActivityOverlay(firstSlotTimestamp, lastSlotTimestamp, ActivityKind.TYPE_NOT_WORN, deviceId, userId, null));
|
||||
firstSlotTimestamp = timestamp;
|
||||
}
|
||||
|
||||
lastSlotTimestamp = timestamp;
|
||||
|
||||
}
|
||||
|
||||
if (firstSlotTimestamp != lastSlotTimestamp)
|
||||
overlayList.add(new HPlusHealthActivityOverlay(firstSlotTimestamp, lastSlotTimestamp, ActivityKind.TYPE_NOT_WORN, deviceId, userId, null));
|
||||
|
||||
overlayDao.insertOrReplaceInTx(overlayList);
|
||||
}
|
||||
|
||||
} catch (GBException ex) {
|
||||
LOG.info((ex.getMessage()));
|
||||
} catch (Exception ex) {
|
||||
@ -329,7 +370,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
* @param data the message from the device
|
||||
* @return boolean indicating success or fail
|
||||
*/
|
||||
public boolean processIncomingSleepData(byte[] data){
|
||||
public boolean processIncomingSleepData(byte[] data) {
|
||||
HPlusDataRecordSleep record;
|
||||
|
||||
try{
|
||||
@ -353,7 +394,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
List<HPlusHealthActivityOverlay> overlayList = new ArrayList<>();
|
||||
List<HPlusDataRecord.RecordInterval> intervals = record.getIntervals();
|
||||
|
||||
for(HPlusDataRecord.RecordInterval interval : intervals){
|
||||
for(HPlusDataRecord.RecordInterval interval : intervals) {
|
||||
overlayList.add(new HPlusHealthActivityOverlay(interval.timestampFrom, interval.timestampTo, interval.activityKind, deviceId, userId, null));
|
||||
}
|
||||
|
||||
@ -386,7 +427,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
public boolean processRealtimeStats(byte[] data, int age) {
|
||||
HPlusDataRecordRealtime record;
|
||||
|
||||
try{
|
||||
try {
|
||||
record = new HPlusDataRecordRealtime(data, age);
|
||||
} catch(IllegalArgumentException e){
|
||||
LOG.info((e.getMessage()));
|
||||
@ -395,24 +436,13 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
|
||||
//Skip duplicated messages as the device seems to send the same record multiple times
|
||||
//This can be used to detect the user is moving (not sleeping)
|
||||
if(prevRealTimeRecord != null && record.same(prevRealTimeRecord))
|
||||
if (prevRealTimeRecord != null && record.same(prevRealTimeRecord))
|
||||
return true;
|
||||
|
||||
prevRealTimeRecord = record;
|
||||
|
||||
getDevice().setBatteryLevel(record.battery);
|
||||
|
||||
//Skip when measuring heart rate
|
||||
//Calories and Distance are updated and these values will be lost.
|
||||
//Because a message with a valid Heart Rate will be provided, this loss very limited
|
||||
if(record.heartRate == ActivityKind.TYPE_NOT_MEASURED) {
|
||||
getDevice().setFirmwareVersion2("---");
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}else {
|
||||
getDevice().setFirmwareVersion2("" + record.heartRate);
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
@ -456,9 +486,9 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
public boolean processDaySummary(byte[] data) {
|
||||
HPlusDataRecordDaySummary record;
|
||||
|
||||
try{
|
||||
try {
|
||||
record = new HPlusDataRecordDaySummary(data);
|
||||
} catch(IllegalArgumentException e){
|
||||
} catch(IllegalArgumentException e) {
|
||||
LOG.info((e.getMessage()));
|
||||
return false;
|
||||
}
|
||||
@ -498,7 +528,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
public boolean processVersion(byte[] data) {
|
||||
int major, minor;
|
||||
|
||||
if(data.length >= 11){
|
||||
if (data.length >= 11) {
|
||||
major = data[10] & 0xFF;
|
||||
minor = data[9] & 0xFF;
|
||||
|
||||
@ -506,8 +536,8 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
int hwMinor = data[1] & 0xFF;
|
||||
|
||||
getDevice().setFirmwareVersion2(hwMajor + "." + hwMinor);
|
||||
mHPlusSupport.setUnicodeSupport((data[3] != 0));
|
||||
}else {
|
||||
mHPlusSupport.setUnicodeSupport(data[3] != 0);
|
||||
} else {
|
||||
major = data[2] & 0xFF;
|
||||
minor = data[1] & 0xFF;
|
||||
}
|
||||
@ -526,7 +556,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
TransactionBuilder builder = new TransactionBuilder("requestSleepStats");
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_SLEEP});
|
||||
mHPlusSupport.performConnected(builder.getTransaction());
|
||||
}catch(Exception e){
|
||||
} catch(Exception e) {
|
||||
|
||||
}
|
||||
|
||||
@ -548,23 +578,23 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
//Sync to current time
|
||||
mGetDaySlotsTime = now;
|
||||
|
||||
if(mSlotsInitialSync) {
|
||||
if(mLastSlotReceived == 143) {
|
||||
if (mSlotsInitialSync) {
|
||||
if (mLastSlotReceived == 143) {
|
||||
mSlotsInitialSync = false;
|
||||
mGetDaySlotsTime.set(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD); //Sync complete. Delay timer forever
|
||||
mLastSlotReceived = -1;
|
||||
mLastSlotRequested = mLastSlotReceived + 1;
|
||||
return;
|
||||
}else {
|
||||
} else {
|
||||
mGetDaySlotsTime.add(Calendar.SECOND, CURRENT_DAY_SYNC_RETRY_PERIOD);
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
//Sync complete. Delay timer forever
|
||||
mGetDaySlotsTime.set(Calendar.SECOND, CURRENT_DAY_SYNC_PERIOD);
|
||||
return;
|
||||
}
|
||||
|
||||
if(mLastSlotReceived == 143)
|
||||
if (mLastSlotReceived == 143)
|
||||
mLastSlotReceived = -1;
|
||||
|
||||
byte hour = (byte) ((mLastSlotReceived + 1)/ 6);
|
||||
@ -581,19 +611,19 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
TransactionBuilder builder = new TransactionBuilder("getNextDaySlot");
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, msg);
|
||||
mHPlusSupport.performConnected(builder.getTransaction());
|
||||
}catch(Exception e){
|
||||
} catch(Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Request a batch of data with the summary of the previous days
|
||||
*/
|
||||
public void requestDaySummaryData(){
|
||||
public void requestDaySummaryData() {
|
||||
try {
|
||||
TransactionBuilder builder = new TransactionBuilder("startSyncDaySummary");
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DAY_DATA});
|
||||
mHPlusSupport.performConnected(builder.getTransaction());
|
||||
}catch(Exception e){
|
||||
} catch(Exception e) {
|
||||
|
||||
}
|
||||
mGetDaySummaryTime = GregorianCalendar.getInstance();
|
||||
@ -606,7 +636,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
* @param timestamp The sample timestamp
|
||||
* @return The sample just created
|
||||
*/
|
||||
private HPlusHealthActivitySample createSample(DBHandler dbHandler, int timestamp){
|
||||
private HPlusHealthActivitySample createSample(DBHandler dbHandler, int timestamp) {
|
||||
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
|
||||
HPlusHealthActivitySample sample = new HPlusHealthActivitySample(
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2016-2017 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
ivanovlev, João Paulo Barraca
|
||||
ivanovlev, João Paulo Barraca, Pavel Motyrev
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -808,6 +808,8 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
private byte[] encodeStringToDevice(String s) {
|
||||
|
||||
List<Byte> outBytes = new ArrayList<Byte>();
|
||||
Boolean unicode = HPlusCoordinator.getUnicodeSupport(this.gbDevice.getAddress());
|
||||
LOG.info("Encode String: Unicode=" + unicode);
|
||||
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
Character c = s.charAt(i);
|
||||
@ -817,13 +819,14 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
cs = HPlusConstants.transliterateMap.get(c);
|
||||
} else {
|
||||
try {
|
||||
if(HPlusCoordinator.getUnicodeSupport(this.gbDevice.getAddress()))
|
||||
if(unicode)
|
||||
cs = c.toString().getBytes("Unicode");
|
||||
else
|
||||
cs = c.toString().getBytes("GB2312");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
//Fallback. Result string may be strange, but better than nothing
|
||||
cs = c.toString().getBytes();
|
||||
LOG.error("Could not convert String to Bytes: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < cs.length; j++)
|
||||
@ -884,7 +887,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
String DEVINFO_STEP = getContext().getString(R.string.chart_steps) + ": ";
|
||||
String DEVINFO_DISTANCE = getContext().getString(R.string.distance) + ": ";
|
||||
String DEVINFO_CALORY = getContext().getString(R.string.calories) + ": ";
|
||||
String DEVINFO_HEART = getContext().getString(R.string.charts_legend_heartrate);
|
||||
String DEVINFO_HEART = getContext().getString(R.string.charts_legend_heartrate) + ": ";
|
||||
|
||||
String info = "";
|
||||
if (record.steps > 0) {
|
||||
|
@ -17,10 +17,7 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.liveview;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.Context;
|
||||
import android.os.ParcelUuid;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -28,158 +25,25 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class LiveviewIoThread extends GBDeviceIoThread {
|
||||
public class LiveviewIoThread extends BtClassicIoThread {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LiveviewIoThread.class);
|
||||
|
||||
private static final UUID SERIAL = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
|
||||
|
||||
private final LiveviewProtocol mLiveviewProtocol;
|
||||
private final LiveviewSupport mLiveviewSupport;
|
||||
|
||||
|
||||
private BluetoothAdapter mBtAdapter = null;
|
||||
private BluetoothSocket mBtSocket = null;
|
||||
private InputStream mInStream = null;
|
||||
private OutputStream mOutStream = null;
|
||||
private boolean mQuit = false;
|
||||
|
||||
@Override
|
||||
public void quit() {
|
||||
mQuit = true;
|
||||
if (mBtSocket != null) {
|
||||
try {
|
||||
mBtSocket.close();
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean mIsConnected = false;
|
||||
|
||||
|
||||
public LiveviewIoThread(GBDevice gbDevice, Context context, GBDeviceProtocol lvProtocol, LiveviewSupport lvSupport, BluetoothAdapter lvBtAdapter) {
|
||||
super(gbDevice, context);
|
||||
mLiveviewProtocol = (LiveviewProtocol) lvProtocol;
|
||||
mBtAdapter = lvBtAdapter;
|
||||
mLiveviewSupport = lvSupport;
|
||||
super(gbDevice, context, lvProtocol, lvSupport, lvBtAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] bytes) {
|
||||
if (null == bytes)
|
||||
return;
|
||||
LOG.debug("writing:" + GB.hexdump(bytes, 0, bytes.length));
|
||||
try {
|
||||
mOutStream.write(bytes);
|
||||
mOutStream.flush();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error writing.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mIsConnected = connect();
|
||||
if (!mIsConnected) {
|
||||
setUpdateState(GBDevice.State.NOT_CONNECTED);
|
||||
return;
|
||||
}
|
||||
mQuit = false;
|
||||
|
||||
while (!mQuit) {
|
||||
LOG.info("Ready for a new message exchange.");
|
||||
|
||||
try {
|
||||
GBDeviceEvent deviceEvents[] = mLiveviewProtocol.decodeResponse(parseIncoming());
|
||||
if (deviceEvents == null) {
|
||||
LOG.info("unhandled message");
|
||||
} else {
|
||||
for (GBDeviceEvent deviceEvent : deviceEvents) {
|
||||
if (deviceEvent == null) {
|
||||
continue;
|
||||
}
|
||||
mLiveviewSupport.evaluateGBDeviceEvent(deviceEvent);
|
||||
}
|
||||
}
|
||||
} catch (SocketTimeoutException ignore) {
|
||||
LOG.debug("socket timeout, we can't help but ignore this");
|
||||
} catch (IOException e) {
|
||||
LOG.info(e.getMessage());
|
||||
mIsConnected = false;
|
||||
mBtSocket = null;
|
||||
mInStream = null;
|
||||
mOutStream = null;
|
||||
LOG.info("Bluetooth socket closed, will quit IO Thread");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mIsConnected = false;
|
||||
if (mBtSocket != null) {
|
||||
try {
|
||||
mBtSocket.close();
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
mBtSocket = null;
|
||||
}
|
||||
setUpdateState(GBDevice.State.NOT_CONNECTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean connect() {
|
||||
GBDevice.State originalState = gbDevice.getState();
|
||||
setUpdateState(GBDevice.State.CONNECTING);
|
||||
|
||||
try {
|
||||
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(gbDevice.getAddress());
|
||||
ParcelUuid uuids[] = btDevice.getUuids();
|
||||
if (uuids == null) {
|
||||
return false;
|
||||
}
|
||||
for (ParcelUuid uuid : uuids) {
|
||||
LOG.info("found service UUID " + uuid);
|
||||
}
|
||||
mBtSocket = btDevice.createRfcommSocketToServiceRecord(uuids[0].getUuid());
|
||||
mBtSocket.connect();
|
||||
mInStream = mBtSocket.getInputStream();
|
||||
mOutStream = mBtSocket.getOutputStream();
|
||||
setUpdateState(GBDevice.State.CONNECTED);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Server socket cannot be started.");
|
||||
//LOG.error(e.getMessage());
|
||||
setUpdateState(originalState);
|
||||
mInStream = null;
|
||||
mOutStream = null;
|
||||
mBtSocket = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
write(mLiveviewProtocol.encodeSetTime());
|
||||
setUpdateState(GBDevice.State.INITIALIZED);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setUpdateState(GBDevice.State state) {
|
||||
gbDevice.setState(state);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
private byte[] parseIncoming() throws IOException {
|
||||
protected byte[] parseIncoming(InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream msgStream = new ByteArrayOutputStream();
|
||||
|
||||
boolean finished = false;
|
||||
@ -187,7 +51,7 @@ public class LiveviewIoThread extends GBDeviceIoThread {
|
||||
byte[] incoming = new byte[1];
|
||||
|
||||
while (!finished) {
|
||||
mInStream.read(incoming);
|
||||
inputStream.read(incoming);
|
||||
msgStream.write(incoming);
|
||||
|
||||
switch (state) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, atkyritsis, Carsten Pfeiffer,
|
||||
Christian Fischer, Daniele Gobbetti, JohnnySun, Julien Pivotto, Kasha,
|
||||
Sergey Trofimov, Steffen Liebergeld
|
||||
Christian Fischer, Daniele Gobbetti, freezed-or-frozen, JohnnySun, Julien
|
||||
Pivotto, Kasha, Sergey Trofimov, Steffen Liebergeld
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -1270,8 +1270,26 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
}
|
||||
|
||||
private void handleSensorData(byte[] value) {
|
||||
int counter=0, step=0, axis1=0, axis2=0, axis3 =0;
|
||||
/**
|
||||
* Analyse and decode sensor data from ADXL362 accelerometer
|
||||
* @param value to decode
|
||||
* @return nothing
|
||||
*
|
||||
* Each axis raw value is 16bits long and look like : ttssvvvvvvvvvvvv
|
||||
* tt : 2 bits for the type of data (00=x, 01=y, 10=z, 11=temperature)
|
||||
* ss : sign of the value
|
||||
* vvvvvvvvvvvv : accelerometer value encoded using two complements
|
||||
*
|
||||
* TODO: Because each accelerometer is different, all values should be calibrated with :
|
||||
* a scale factor
|
||||
* an offset factor
|
||||
*/
|
||||
private static void handleSensorData(byte[] value) {
|
||||
int counter=0, step=0;
|
||||
double xAxis=0.0, yAxis=0.0, zAxis=0.0;
|
||||
double scale_factor = 1000.0;
|
||||
double gravity = 9.81;
|
||||
|
||||
if ((value.length - 2) % 6 != 0) {
|
||||
LOG.warn("GOT UNEXPECTED SENSOR DATA WITH LENGTH: " + value.length);
|
||||
for (byte b : value) {
|
||||
@ -1282,11 +1300,46 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
counter = (value[0] & 0xff) | ((value[1] & 0xff) << 8);
|
||||
for (int idx = 0; idx < ((value.length - 2) / 6); idx++) {
|
||||
step = idx * 6;
|
||||
axis1 = (value[step+2] & 0xff) | ((value[step+3] & 0xff) << 8);
|
||||
axis2 = (value[step+4] & 0xff) | ((value[step+5] & 0xff) << 8);
|
||||
axis3 = (value[step+6] & 0xff) | ((value[step+7] & 0xff) << 8);
|
||||
|
||||
// Analyse X-axis data
|
||||
int xAxisRawValue = (value[step+2] & 0xff) | ((value[step+3] & 0xff) << 8);
|
||||
int xAxisSign = (value[step+3] & 0x30) >> 4;
|
||||
int xAxisType = (value[step+3] & 0xc0) >> 6;
|
||||
if (xAxisSign == 0) {
|
||||
xAxis = xAxisRawValue & 0xfff;
|
||||
}
|
||||
else {
|
||||
xAxis = (xAxisRawValue & 0xfff) - 4097;
|
||||
}
|
||||
xAxis = (xAxis*1.0 / scale_factor) * gravity;
|
||||
|
||||
// Analyse Y-axis data
|
||||
int yAxisRawValue = (value[step+4] & 0xff) | ((value[step+5] & 0xff) << 8);
|
||||
int yAxisSign = (value[step+5] & 0x30) >> 4;
|
||||
int yAxisType = (value[step+5] & 0xc0) >> 6;
|
||||
if (yAxisSign == 0) {
|
||||
yAxis = yAxisRawValue & 0xfff;
|
||||
}
|
||||
else {
|
||||
yAxis = (yAxisRawValue & 0xfff) - 4097;
|
||||
}
|
||||
yAxis = (yAxis / scale_factor) * gravity;
|
||||
|
||||
// Analyse Z-axis data
|
||||
int zAxisRawValue = (value[step+6] & 0xff) | ((value[step+7] & 0xff) << 8);
|
||||
int zAxisSign = (value[step+7] & 0x30) >> 4;
|
||||
int zAxisType = (value[step+7] & 0xc0) >> 6;
|
||||
if (zAxisSign == 0) {
|
||||
zAxis = zAxisRawValue & 0xfff;
|
||||
}
|
||||
else {
|
||||
zAxis = (zAxisRawValue & 0xfff) - 4097;
|
||||
}
|
||||
zAxis = (zAxis / scale_factor) * gravity;
|
||||
|
||||
// Print results in log
|
||||
LOG.info("READ SENSOR DATA VALUES: counter:"+counter+" step:"+step+" x-axis:"+ String.format("%.03f",xAxis)+" y-axis:"+String.format("%.03f",yAxis)+" z-axis:"+String.format("%.03f",zAxis)+";");
|
||||
}
|
||||
LOG.info("READ SENSOR DATA VALUES: counter:"+counter+" step:"+step+" axis1:"+axis1+" axis2:"+axis2+" axis3:"+axis3+";");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -19,8 +19,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2;
|
||||
public enum FirmwareType {
|
||||
FIRMWARE((byte) 0),
|
||||
FONT((byte) 1),
|
||||
UNKNOWN1((byte) 2),
|
||||
UNKNOWN2((byte) 3),
|
||||
RES((byte) 2),
|
||||
GPS((byte) 3),
|
||||
INVALID(Byte.MIN_VALUE);
|
||||
|
||||
private final byte value;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -53,7 +53,7 @@ public class Mi2FirmwareInfo {
|
||||
0x4b
|
||||
};
|
||||
|
||||
private static Map<Integer,String> crcToVersion = new HashMap<>();
|
||||
protected static Map<Integer,String> crcToVersion = new HashMap<>();
|
||||
static {
|
||||
// firmware
|
||||
crcToVersion.put(41899, "1.0.0.39");
|
||||
@ -89,7 +89,7 @@ public class Mi2FirmwareInfo {
|
||||
firmwareType = determineFirmwareType(bytes);
|
||||
}
|
||||
|
||||
private FirmwareType determineFirmwareType(byte[] bytes) {
|
||||
protected FirmwareType determineFirmwareType(byte[] bytes) {
|
||||
if (ArrayUtils.startsWith(bytes, FT_HEADER)) {
|
||||
return FirmwareType.FONT;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -18,7 +18,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
|
||||
@ -67,11 +66,11 @@ public class Mi2TextNotificationStrategy extends Mi2NotificationStrategy {
|
||||
if (simpleNotification != null) {
|
||||
switch (simpleNotification.getAlertCategory()) {
|
||||
case Email:
|
||||
return new byte[] { BLETypeConversions.fromUint8(MiBand2Service.ALERT_LEVEL_MESSAGE), BLETypeConversions.fromUint8(numAlerts)};
|
||||
return new byte[] { BLETypeConversions.fromUint8(AlertCategory.Email.getId()), BLETypeConversions.fromUint8(numAlerts)};
|
||||
case InstantMessage:
|
||||
return new byte[] { BLETypeConversions.fromUint8(MiBand2Service.ALERT_LEVEL_CUSTOM), BLETypeConversions.fromUint8(numAlerts), MiBand2Service.ICON_CHAT};
|
||||
return new byte[] { BLETypeConversions.fromUint8(AlertCategory.CustomMiBand2.getId()), BLETypeConversions.fromUint8(numAlerts), MiBand2Service.ICON_CHAT};
|
||||
case News:
|
||||
return new byte[] { BLETypeConversions.fromUint8(MiBand2Service.ALERT_LEVEL_CUSTOM), BLETypeConversions.fromUint8(numAlerts), MiBand2Service.ICON_PENGUIN};
|
||||
return new byte[] { BLETypeConversions.fromUint8(AlertCategory.CustomMiBand2.getId()), BLETypeConversions.fromUint8(numAlerts), MiBand2Service.ICON_PENGUIN};
|
||||
}
|
||||
}
|
||||
return new byte[] { BLETypeConversions.fromUint8(AlertCategory.SMS.getId()), BLETypeConversions.fromUint8(numAlerts)};
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Christian
|
||||
Fischer, Daniele Gobbetti, JohnnySun, Julien Pivotto, Kasha, Sergey Trofimov,
|
||||
Steffen Liebergeld
|
||||
Fischer, Daniele Gobbetti, JohnnySun, José Rebelo, Julien Pivotto, Kasha,
|
||||
Sergey Trofimov, Steffen Liebergeld
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -36,8 +36,10 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -50,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInf
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
|
||||
@ -298,7 +301,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
return this;
|
||||
}
|
||||
|
||||
private NotificationStrategy getNotificationStrategy() {
|
||||
public NotificationStrategy getNotificationStrategy() {
|
||||
String firmwareVersion = getDevice().getFirmwareVersion();
|
||||
if (firmwareVersion != null) {
|
||||
Version ver = new Version(firmwareVersion);
|
||||
@ -432,7 +435,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
}
|
||||
|
||||
private void performPreferredNotification(String task, String notificationOrigin, SimpleNotification simpleNotification, int alertLevel, BtLEAction extraAction) {
|
||||
protected void performPreferredNotification(String task, String notificationOrigin, SimpleNotification simpleNotification, int alertLevel, BtLEAction extraAction) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized(task);
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
@ -526,7 +529,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
performPreferredNotification(origin + " received", origin, simpleNotification, alertLevel, null);
|
||||
}
|
||||
|
||||
private void onAlarmClock(NotificationSpec notificationSpec) {
|
||||
protected void onAlarmClock(NotificationSpec notificationSpec) {
|
||||
alarmClockRinging = true;
|
||||
AbortTransactionAction abortAction = new StopNotificationAction(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL)) {
|
||||
@Override
|
||||
@ -805,7 +808,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleButtonPressed(byte[] value) {
|
||||
public void handleButtonPressed(byte[] value) {
|
||||
LOG.info("Button pressed");
|
||||
logMessageContent(value);
|
||||
}
|
||||
@ -1075,12 +1078,35 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
case MiBandConst.PREF_MI2_DATEFORMAT:
|
||||
setDateDisplay(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_GOAL_NOTIFICATION:
|
||||
setGoalNotification(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT:
|
||||
setActivateDisplayOnLiftWrist(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_DISPLAY_ITEMS:
|
||||
setDisplayItems(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO:
|
||||
setRotateWristToSwitchInfo(builder);
|
||||
break;
|
||||
case ActivityUser.PREF_USER_STEPS_GOAL:
|
||||
setFitnessGoal(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_DO_NOT_DISTURB:
|
||||
case MiBandConst.PREF_MI2_DO_NOT_DISTURB_START:
|
||||
case MiBandConst.PREF_MI2_DO_NOT_DISTURB_END:
|
||||
setDoNotDisturb(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS:
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD:
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_START:
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_END:
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND:
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND_START:
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND_END:
|
||||
setInactivityWarnings(builder);
|
||||
break;
|
||||
}
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
@ -1128,6 +1154,17 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setGoalNotification(TransactionBuilder builder) {
|
||||
boolean enable = MiBand2Coordinator.getGoalNotification();
|
||||
LOG.info("Setting goal notification to " + enable);
|
||||
if (enable) {
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_ENABLE_GOAL_NOTIFICATION);
|
||||
} else {
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DISABLE_GOAL_NOTIFICATION);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setActivateDisplayOnLiftWrist(TransactionBuilder builder) {
|
||||
boolean enable = MiBand2Coordinator.getActivateDisplayOnLiftWrist();
|
||||
LOG.info("Setting activate display on lift wrist to " + enable);
|
||||
@ -1139,6 +1176,137 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setDisplayItems(TransactionBuilder builder) {
|
||||
Set<String> pages = MiBand2Coordinator.getDisplayItems();
|
||||
LOG.info("Setting display items to " + (pages == null ? "none" : pages));
|
||||
|
||||
byte[] data = MiBand2Service.COMMAND_CHANGE_SCREENS.clone();
|
||||
|
||||
if (pages != null) {
|
||||
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_STEPS)) {
|
||||
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_STEPS;
|
||||
}
|
||||
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_DISTANCE)) {
|
||||
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_DISTANCE;
|
||||
}
|
||||
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_CALORIES)) {
|
||||
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_CALORIES;
|
||||
}
|
||||
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_HEART_RATE)) {
|
||||
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_HEART_RATE;
|
||||
}
|
||||
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_BATTERY)) {
|
||||
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_BATTERY;
|
||||
}
|
||||
}
|
||||
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setRotateWristToSwitchInfo(TransactionBuilder builder) {
|
||||
boolean enable = MiBand2Coordinator.getRotateWristToSwitchInfo();
|
||||
LOG.info("Setting rotate wrist to cycle info to " + enable);
|
||||
if (enable) {
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_ENABLE_ROTATE_WRIST_TO_SWITCH_INFO);
|
||||
} else {
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DISABLE_ROTATE_WRIST_TO_SWITCH_INFO);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setDisplayCaller(TransactionBuilder builder) {
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_ENABLE_DISPLAY_CALLER);
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setDoNotDisturb(TransactionBuilder builder) {
|
||||
DoNotDisturb doNotDisturb = MiBand2Coordinator.getDoNotDisturb(getContext());
|
||||
LOG.info("Setting do not disturb to " + doNotDisturb);
|
||||
switch (doNotDisturb) {
|
||||
case OFF:
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DO_NOT_DISTURB_OFF);
|
||||
break;
|
||||
case AUTOMATIC:
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DO_NOT_DISTURB_AUTOMATIC);
|
||||
break;
|
||||
case SCHEDULED:
|
||||
byte[] data = MiBand2Service.COMMAND_DO_NOT_DISTURB_SCHEDULED.clone();
|
||||
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
|
||||
Date start = MiBand2Coordinator.getDoNotDisturbStart();
|
||||
calendar.setTime(start);
|
||||
data[MiBand2Service.DND_BYTE_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.DND_BYTE_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
Date end = MiBand2Coordinator.getDoNotDisturbEnd();
|
||||
calendar.setTime(end);
|
||||
data[MiBand2Service.DND_BYTE_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.DND_BYTE_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setInactivityWarnings(TransactionBuilder builder) {
|
||||
boolean enable = MiBand2Coordinator.getInactivityWarnings();
|
||||
LOG.info("Setting inactivity warnings to " + enable);
|
||||
|
||||
if (enable) {
|
||||
byte[] data = MiBand2Service.COMMAND_ENABLE_INACTIVITY_WARNINGS.clone();
|
||||
|
||||
int threshold = MiBand2Coordinator.getInactivityWarningsThreshold();
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_THRESHOLD] = (byte) threshold;
|
||||
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
|
||||
boolean enableDnd = MiBand2Coordinator.getInactivityWarningsDnd();
|
||||
|
||||
Date intervalStart = MiBand2Coordinator.getInactivityWarningsStart();
|
||||
Date intervalEnd = MiBand2Coordinator.getInactivityWarningsEnd();
|
||||
Date dndStart = MiBand2Coordinator.getInactivityWarningsDndStart();
|
||||
Date dndEnd = MiBand2Coordinator.getInactivityWarningsDndEnd();
|
||||
|
||||
// The first interval always starts when the warnings interval starts
|
||||
calendar.setTime(intervalStart);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
if(enableDnd) {
|
||||
// The first interval ends when the dnd interval starts
|
||||
calendar.setTime(dndStart);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
// The second interval starts when the dnd interval ends
|
||||
calendar.setTime(dndEnd);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_2_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_2_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
// ... and it ends when the warnings interval ends
|
||||
calendar.setTime(intervalEnd);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_2_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_2_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
} else {
|
||||
// No Dnd, use the first interval
|
||||
calendar.setTime(intervalEnd);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
}
|
||||
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
|
||||
} else {
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DISABLE_INACTIVITY_WARNINGS);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void phase2Initialize(TransactionBuilder builder) {
|
||||
LOG.info("phase2Initialize...");
|
||||
enableFurtherNotifications(builder, true);
|
||||
@ -1147,7 +1315,13 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
setTimeFormat(builder);
|
||||
setWearLocation(builder);
|
||||
setFitnessGoal(builder);
|
||||
setDisplayItems(builder);
|
||||
setDoNotDisturb(builder);
|
||||
setRotateWristToSwitchInfo(builder);
|
||||
setActivateDisplayOnLiftWrist(builder);
|
||||
setDisplayCaller(builder);
|
||||
setGoalNotification(builder);
|
||||
setInactivityWarnings(builder);
|
||||
setHeartrateSleepSupport(builder);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.util.TimeUtils;
|
||||
import android.text.format.DateUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -66,8 +68,9 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
|
||||
|
||||
private List<MiBandActivitySample> samples = new ArrayList<>(60*24); // 1day per default
|
||||
|
||||
private byte lastPacketCounter = -1;
|
||||
private byte lastPacketCounter;
|
||||
private Calendar startTimestamp;
|
||||
private int fetchCount;
|
||||
|
||||
public FetchActivityOperation(MiBand2Support support) {
|
||||
super(support);
|
||||
@ -83,12 +86,25 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
|
||||
|
||||
@Override
|
||||
protected void doPerform() throws IOException {
|
||||
startFetching();
|
||||
}
|
||||
|
||||
private void startFetching() throws IOException {
|
||||
samples.clear();
|
||||
lastPacketCounter = -1;
|
||||
|
||||
TransactionBuilder builder = performInitialized("fetching activity data");
|
||||
getSupport().setLowLatency(builder);
|
||||
builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
|
||||
if (fetchCount == 0) {
|
||||
builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
|
||||
}
|
||||
fetchCount++;
|
||||
|
||||
BluetoothGattCharacteristic characteristicActivityData = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA);
|
||||
builder.notify(characteristicActivityData, false);
|
||||
|
||||
BluetoothGattCharacteristic characteristicFetch = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC4);
|
||||
builder.notify(characteristicFetch, true);
|
||||
BluetoothGattCharacteristic characteristicActivityData = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA);
|
||||
|
||||
GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
|
||||
builder.write(characteristicFetch, BLETypeConversions.join(new byte[] { MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE, 0x01 }, getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES)));
|
||||
@ -136,13 +152,40 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
|
||||
}
|
||||
|
||||
private void handleActivityFetchFinish() {
|
||||
LOG.info("Fetching activity data has finished.");
|
||||
saveSamples();
|
||||
LOG.info("Fetching activity data has finished round " + fetchCount);
|
||||
GregorianCalendar lastSyncTimestamp = saveSamples();
|
||||
if (lastSyncTimestamp != null && needsAnotherFetch(lastSyncTimestamp)) {
|
||||
try {
|
||||
startFetching();
|
||||
return;
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Error starting another round of fetching activity data", ex);
|
||||
}
|
||||
}
|
||||
|
||||
operationFinished();
|
||||
unsetBusy();
|
||||
}
|
||||
|
||||
private void saveSamples() {
|
||||
private boolean needsAnotherFetch(GregorianCalendar lastSyncTimestamp) {
|
||||
if (fetchCount > 5) {
|
||||
LOG.warn("Already jave 5 fetch rounds, not doing another one.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DateUtils.isToday(lastSyncTimestamp.getTimeInMillis())) {
|
||||
LOG.info("Hopefully no further fetch needed, last synced timestamp is from today.");
|
||||
return false;
|
||||
}
|
||||
if (lastSyncTimestamp.getTimeInMillis() > System.currentTimeMillis()) {
|
||||
LOG.warn("Not doing another fetch since last synced timestamp is in the future: " + DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime()));
|
||||
return false;
|
||||
}
|
||||
LOG.info("Doing another fetch since last sync timestamp is still too old: " + DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime()));
|
||||
return true;
|
||||
}
|
||||
|
||||
private GregorianCalendar saveSamples() {
|
||||
if (samples.size() > 0) {
|
||||
// save all the samples that we got
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
@ -168,6 +211,7 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
|
||||
|
||||
saveLastSyncTimestamp(timestamp);
|
||||
LOG.info("Mi2 activity data: last sample timestamp: " + DateTimeUtils.formatDateTime(timestamp.getTime()));
|
||||
return timestamp;
|
||||
|
||||
} catch (Exception ex) {
|
||||
GB.toast(getContext(), "Error saving activity samples", Toast.LENGTH_LONG, GB.ERROR);
|
||||
@ -175,6 +219,7 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
|
||||
samples.clear();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -48,11 +48,11 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
public class UpdateFirmwareOperation extends AbstractMiBand2Operation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpdateFirmwareOperation.class);
|
||||
|
||||
private final Uri uri;
|
||||
private final BluetoothGattCharacteristic fwCControlChar;
|
||||
private final BluetoothGattCharacteristic fwCDataChar;
|
||||
final Prefs prefs = GBApplication.getPrefs();
|
||||
private Mi2FirmwareInfo firmwareInfo;
|
||||
protected final Uri uri;
|
||||
protected final BluetoothGattCharacteristic fwCControlChar;
|
||||
protected final BluetoothGattCharacteristic fwCDataChar;
|
||||
protected final Prefs prefs = GBApplication.getPrefs();
|
||||
protected Mi2FirmwareInfo firmwareInfo;
|
||||
|
||||
public UpdateFirmwareOperation(Uri uri, MiBand2Support support) {
|
||||
super(support);
|
||||
@ -82,7 +82,7 @@ public class UpdateFirmwareOperation extends AbstractMiBand2Operation {
|
||||
//the firmware will be sent by the notification listener if the band confirms that the metadata are ok.
|
||||
}
|
||||
|
||||
private void done() {
|
||||
protected void done() {
|
||||
LOG.info("Operation done.");
|
||||
operationFinished();
|
||||
unsetBusy();
|
||||
@ -163,7 +163,7 @@ public class UpdateFirmwareOperation extends AbstractMiBand2Operation {
|
||||
done();
|
||||
}
|
||||
}
|
||||
private void displayMessage(Context context, String message, int duration, int severity) {
|
||||
protected void displayMessage(Context context, String message, int duration, int severity) {
|
||||
getSupport().handleGBDeviceEvent(new GBDeviceEventDisplayMessage(message, duration, severity));
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,6 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -158,9 +157,11 @@ class AppMessageHandlerMorpheuz extends AppMessageHandler {
|
||||
int version = (int) pair.second;
|
||||
LOG.info("got version: " + ((float) version / 10.0f));
|
||||
ctrl_message |= CTRL_VERSION_DONE;
|
||||
} else if (pair.first.equals(keyBase)) {// fix timestamp
|
||||
TimeZone tz = SimpleTimeZone.getDefault();
|
||||
recording_base_timestamp = (int) pair.second - (tz.getOffset(System.currentTimeMillis())) / 1000;
|
||||
} else if (pair.first.equals(keyBase)) {
|
||||
recording_base_timestamp = (int) pair.second;
|
||||
if (mPebbleProtocol.mFwMajor < 3) {
|
||||
recording_base_timestamp -= SimpleTimeZone.getDefault().getOffset(recording_base_timestamp * 1000L) / 1000;
|
||||
}
|
||||
LOG.info("got base: " + recording_base_timestamp);
|
||||
ctrl_message |= CTRL_SET_LAST_SENT | CTRL_DO_NEXT;
|
||||
} else if (pair.first.equals(keyAutoReset)) {
|
||||
|
@ -156,7 +156,7 @@ class PebbleIoThread extends GBDeviceIoThread {
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOG.warn("error while connecting: " + e.getMessage(), e);
|
||||
gbDevice.setState(originalState);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
|
||||
|
@ -87,6 +87,8 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
|
||||
byteArray[i] = ((Integer) jsonArray.get(i)).byteValue();
|
||||
}
|
||||
object = byteArray;
|
||||
} else if (object instanceof Boolean) {
|
||||
object = (short) (((Boolean) object) ? 1 : 0);
|
||||
}
|
||||
pairs.add(new Pair<>(Integer.parseInt(keyStr), object));
|
||||
}
|
||||
|
@ -164,7 +164,14 @@ class PebbleGATTClient extends BluetoothGattCallback {
|
||||
if (doPairing) {
|
||||
BluetoothGattCharacteristic characteristic = gatt.getService(SERVICE_UUID).getCharacteristic(PAIRING_TRIGGER_CHARACTERISTIC);
|
||||
if ((characteristic.getProperties() & PROPERTY_WRITE) != 0) {
|
||||
characteristic.setValue(new byte[]{1});
|
||||
LOG.info("This seems to be a >=4.0 FW Pebble, writing to pairing trigger");
|
||||
// flags:
|
||||
// 0 - always 1
|
||||
// 1 - unknown
|
||||
// 2 - always 0
|
||||
// 3 - unknown, set on kitkat (seems to help to get a "better" pairing)
|
||||
// 4 - unknown, set on some phones
|
||||
characteristic.setValue(new byte[]{9});
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
} else {
|
||||
LOG.info("This seems to be some <4.0 FW Pebble, reading pairing trigger");
|
||||
|
@ -39,6 +39,7 @@ public class GBCallControlReceiver extends BroadcastReceiver {
|
||||
GBDeviceEventCallControl.Event callCmd = GBDeviceEventCallControl.Event.values()[intent.getIntExtra("event", 0)];
|
||||
switch (callCmd) {
|
||||
case END:
|
||||
case REJECT:
|
||||
case START:
|
||||
try {
|
||||
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
@ -46,7 +47,7 @@ public class GBCallControlReceiver extends BroadcastReceiver {
|
||||
Method method = clazz.getDeclaredMethod("getITelephony");
|
||||
method.setAccessible(true);
|
||||
ITelephony telephonyService = (ITelephony) method.invoke(telephonyManager);
|
||||
if (callCmd == GBDeviceEventCallControl.Event.END) {
|
||||
if (callCmd == GBDeviceEventCallControl.Event.END || callCmd == GBDeviceEventCallControl.Event.REJECT) {
|
||||
telephonyService.endCall();
|
||||
} else {
|
||||
telephonyService.answerRingingCall();
|
||||
|
@ -16,12 +16,20 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.os.ParcelUuid;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class AndroidUtils {
|
||||
public static ParcelUuid[] toParcelUUids(Parcelable[] uuids) {
|
||||
if (uuids == null) {
|
||||
@ -61,4 +69,48 @@ public class AndroidUtils {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLanguage(Activity activity, Locale language) {
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(language);
|
||||
|
||||
// FIXME: I have no idea what I am doing
|
||||
activity.getBaseContext().getResources().updateConfiguration(config, activity.getBaseContext().getResources().getDisplayMetrics());
|
||||
activity.recreate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the theme dependent text color as a css-style hex string.
|
||||
* @param context the context to access the colour
|
||||
*/
|
||||
public static String getTextColorHex(Context context) {
|
||||
int color;
|
||||
if (GBApplication.isDarkThemeEnabled()) {
|
||||
color = context.getResources().getColor(R.color.primarytext_dark);
|
||||
} else {
|
||||
color = context.getResources().getColor(R.color.primarytext_light);
|
||||
}
|
||||
return colorToHex(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the theme dependent background color as a css-style hex string.
|
||||
* @param context the context to access the colour
|
||||
*/
|
||||
public static String getBackgroundColorHex(Context context) {
|
||||
int color;
|
||||
if (GBApplication.isDarkThemeEnabled()) {
|
||||
color = context.getResources().getColor(R.color.cardview_dark_background);
|
||||
} else {
|
||||
color = context.getResources().getColor(R.color.cardview_light_background);
|
||||
}
|
||||
return colorToHex(color);
|
||||
}
|
||||
|
||||
private static String colorToHex(int color) {
|
||||
return "#"
|
||||
+ Integer.toHexString(Color.red(color))
|
||||
+ Integer.toHexString(Color.green(color))
|
||||
+ Integer.toHexString(Color.blue(color));
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipCooordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.MakibesF68Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewCoordinator;
|
||||
@ -184,6 +185,7 @@ public class DeviceHelper {
|
||||
|
||||
private List<DeviceCoordinator> createCoordinators() {
|
||||
List<DeviceCoordinator> result = new ArrayList<>();
|
||||
result.add(new AmazfitBipCooordinator()); // Note: AmazfitBip must come before MiBand2 because detection is hacky, atm
|
||||
result.add(new MiBand2Coordinator()); // Note: MiBand2 must come before MiBand because detection is hacky, atm
|
||||
result.add(new MiBandCoordinator());
|
||||
result.add(new PebbleCoordinator());
|
||||
|
@ -76,7 +76,7 @@ public class GB {
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(true);
|
||||
if (GBApplication.isRunningLollipopOrLater()) {
|
||||
builder.setVisibility(Notification.VISIBILITY_PUBLIC);
|
||||
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
|
||||
}
|
||||
if (GBApplication.minimizeNotification()) {
|
||||
builder.setPriority(Notification.PRIORITY_MIN);
|
||||
@ -268,7 +268,7 @@ public class GB {
|
||||
notificationIntent, 0);
|
||||
|
||||
NotificationCompat.Builder nb = new NotificationCompat.Builder(context)
|
||||
.setVisibility(Notification.VISIBILITY_PUBLIC)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentTitle(context.getString(R.string.app_name))
|
||||
.setContentText(text)
|
||||
.setContentIntent(pendingIntent)
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -21,6 +21,7 @@ import java.util.Date;
|
||||
|
||||
public class GBPrefs {
|
||||
public static final String PACKAGE_BLACKLIST = "package_blacklist";
|
||||
public static final String CALENDAR_BLACKLIST = "calendar_blacklist";
|
||||
public static final String AUTO_RECONNECT = "general_autocreconnect";
|
||||
private static final String AUTO_START = "general_autostartonboot";
|
||||
private static final boolean AUTO_START_DEFAULT = true;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017 Alberto, Carsten Pfeiffer
|
||||
/* Copyright (C) 2017 Alberto, Carsten Pfeiffer, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -17,6 +17,12 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Xml;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
@ -27,12 +33,6 @@ import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Xml;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
|
||||
public class ImportExportSharedPreferences {
|
||||
@ -125,12 +125,19 @@ public class ImportExportSharedPreferences {
|
||||
editor.putString(key, text);
|
||||
} else if (HASHSET.equals(name)) {
|
||||
if (key.equals(GBPrefs.PACKAGE_BLACKLIST)) {
|
||||
Set<String> blacklist = new HashSet<>();
|
||||
Set<String> apps_blacklist = new HashSet<>();
|
||||
text=text.replace("[","").replace("]","");
|
||||
for (int z=0;z<text.split(",").length;z++){
|
||||
blacklist.add(text.split(",")[z].trim());
|
||||
apps_blacklist.add(text.split(",")[z].trim());
|
||||
}
|
||||
GBApplication.setBlackList(blacklist);
|
||||
GBApplication.setAppsBlackList(apps_blacklist);
|
||||
} else if (key.equals(GBPrefs.CALENDAR_BLACKLIST)) { //TODO: untested
|
||||
Set<String> calendars_blacklist = new HashSet<>();
|
||||
text = text.replace("[", "").replace("]", "");
|
||||
for (int z = 0; z < text.split(",").length; z++) {
|
||||
calendars_blacklist.add(text.split(",")[z].trim());
|
||||
}
|
||||
GBApplication.setCalendarsBlackList(calendars_blacklist);
|
||||
}
|
||||
} else if (!PREFERENCES.equals(name)) {
|
||||
throw new Exception("Unkown type " + name);
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2017 ivanovlev, Yaron Shahrabani
|
||||
/* Copyright (C) 2017 Andreas Shimokawa, ivanovlev, lazarosfs, Yaron
|
||||
Shahrabani
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -28,7 +29,12 @@ public class LanguageUtils {
|
||||
private static Map<Character, String> transliterateMap = new HashMap<Character, String>(){
|
||||
{
|
||||
//extended ASCII characters
|
||||
put('æ', "ae"); put('œ', "oe"); put('ß', "B"); put('ª', "a"); put('º', "o"); put('«',"\""); put('»',"\"");
|
||||
put('æ', "ae"); put('œ', "oe"); put('ª', "a"); put('º', "o"); put('«',"\""); put('»',"\"");
|
||||
|
||||
//german characters
|
||||
put('ä',"ae"); put('ö',"oe"); put('ü',"ue");
|
||||
put('Ä',"Ae"); put('Ö',"Oe"); put('Ü',"Üe");
|
||||
put('ß',"ss"); put('ẞ',"SS");
|
||||
|
||||
//russian chars
|
||||
put('а', "a"); put('б', "b"); put('в', "v"); put('г', "g"); put('д', "d"); put('е', "e"); put('ё', "jo"); put('ж', "zh");
|
||||
@ -42,7 +48,19 @@ public class LanguageUtils {
|
||||
put('ט', "t"); put('י', "y"); put('כ', "c"); put('ל', "l"); put('מ', "m"); put('נ', "n"); put('ס', "s"); put('ע', "'");
|
||||
put('פ', "p"); put('צ', "ts"); put('ק', "k"); put('ר', "r"); put('ש', "sh"); put('ת', "th"); put('ף', "f"); put('ץ', "ts");
|
||||
put('ך', "ch");put('ם', "m");put('ן', "n");
|
||||
//continue for other languages...
|
||||
|
||||
// greek chars
|
||||
put('α',"a");put('ά',"a");put('β',"v");put('γ',"g");put('δ',"d");put('ε',"e");put('έ',"e");put('ζ',"z");put('η',"i");
|
||||
put('ή',"i");put('θ',"th");put('ι',"i");put('ί',"i");put('ϊ',"i");put('ΐ',"i");put('κ',"k");put('λ',"l");put('μ',"m");
|
||||
put('ν',"n");put('ξ',"ks");put('ο',"o");put('ό',"o");put('π',"p");put('ρ',"r");put('σ',"s");put('ς',"s");put('τ',"t");
|
||||
put('υ',"y");put('ύ',"y");put('ϋ',"y");put('ΰ',"y");put('φ',"f");put('χ',"ch");put('ψ',"ps");put('ω',"o");put('ώ',"o");
|
||||
put('Α',"A");put('Ά',"A");put('Β',"B");put('Γ',"G");put('Δ',"D");put('Ε',"E");put('Έ',"E");put('Ζ',"Z");put('Η',"I");
|
||||
put('Ή',"I");put('Θ',"TH");put('Ι',"I");put('Ί',"I");put('Ϊ',"I");put('Κ',"K");put('Λ',"L");put('Μ',"M");put('Ν',"N");
|
||||
put('Ξ',"KS");put('Ο',"O");put('Ό',"O");put('Π',"P");put('Ρ',"R");put('Σ',"S");put('Τ',"T");put('Υ',"Y");put('Ύ',"Y");
|
||||
put('Ϋ',"Y");put('Φ',"F");put('Χ',"CH");put('Ψ',"PS");put('Ω',"O");put('Ώ',"O");
|
||||
|
||||
//TODO: these must be configurabe. If someone wants to transliterate cyrillic it does not mean his device has no German umlauts
|
||||
//all or nothing is really bad here
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -162,4 +163,16 @@ public class Prefs {
|
||||
public SharedPreferences getPreferences() {
|
||||
return preferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ugly workaround for Set<String> preferences not consistently applying.
|
||||
* @param editor
|
||||
* @param preference
|
||||
* @param value
|
||||
*/
|
||||
public static void putStringSet(SharedPreferences.Editor editor, String preference, HashSet<String> value) {
|
||||
editor.putStringSet(preference, null);
|
||||
editor.commit();
|
||||
editor.putStringSet(preference, new HashSet<>(value));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,116 @@
|
||||
/* Copyright (C) 2017 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.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.preference.DialogPreference;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.TimePicker;
|
||||
|
||||
public class TimePreference extends DialogPreference {
|
||||
private int hour = 0;
|
||||
private int minute = 0;
|
||||
|
||||
private TimePicker picker = null;
|
||||
|
||||
public TimePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateDialogView() {
|
||||
picker = new TimePicker(getContext());
|
||||
picker.setIs24HourView(DateFormat.is24HourFormat(getContext()));
|
||||
picker.setPadding(0, 50, 0, 50);
|
||||
|
||||
return picker;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(View v) {
|
||||
super.onBindDialogView(v);
|
||||
|
||||
picker.setCurrentHour(hour);
|
||||
picker.setCurrentMinute(minute);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
super.onDialogClosed(positiveResult);
|
||||
|
||||
if (positiveResult) {
|
||||
hour = picker.getCurrentHour();
|
||||
minute = picker.getCurrentMinute();
|
||||
|
||||
String time = getTime24h();
|
||||
|
||||
if (callChangeListener(time)) {
|
||||
persistString(time);
|
||||
|
||||
updateSummary();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object onGetDefaultValue(TypedArray a, int index) {
|
||||
return a.getString(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
|
||||
String time;
|
||||
|
||||
if (restoreValue) {
|
||||
if (defaultValue == null) {
|
||||
time = getPersistedString("00:00");
|
||||
} else {
|
||||
time = getPersistedString(defaultValue.toString());
|
||||
}
|
||||
} else {
|
||||
time = defaultValue.toString();
|
||||
}
|
||||
|
||||
String[] pieces = time.split(":");
|
||||
|
||||
hour = Integer.parseInt(pieces[0]);
|
||||
minute = Integer.parseInt(pieces[1]);
|
||||
|
||||
updateSummary();
|
||||
}
|
||||
|
||||
public void updateSummary() {
|
||||
if (DateFormat.is24HourFormat(getContext()))
|
||||
setSummary(getTime24h());
|
||||
else
|
||||
setSummary(getTime12h());
|
||||
}
|
||||
|
||||
public String getTime24h() {
|
||||
return String.format("%02d", hour) + ":" + String.format("%02d", minute);
|
||||
}
|
||||
|
||||
public String getTime12h() {
|
||||
String suffix = hour < 12 ? " AM" : " PM";
|
||||
int h = hour > 12 ? hour - 12 : hour;
|
||||
|
||||
return String.valueOf(h) + ":" + String.format("%02d", minute) + suffix;
|
||||
}
|
||||
}
|
@ -28,7 +28,9 @@ public class Version implements Comparable<Version> {
|
||||
public Version(String version) {
|
||||
if(version == null)
|
||||
throw new IllegalArgumentException("Version can not be null");
|
||||
if(!version.matches("[0-9]+(\\.[0-9]+)*"))
|
||||
|
||||
version = version.trim();
|
||||
if (!version.matches("[0-9]+(\\.[0-9]+)*"))
|
||||
throw new IllegalArgumentException("Invalid version format");
|
||||
this.version = version;
|
||||
}
|
||||
|
BIN
app/src/main/res/drawable-hdpi/ic_device_default.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.8 KiB |