Compare commits

...

2049 Commits

Author SHA1 Message Date
Nicolò Balzarotti b6ae04383d HERE: graphic reorganization, inizial presets support! 2017-10-11 23:25:49 +02:00
Nicolò Balzarotti 6c313e2e9b All effects working! 2017-10-11 01:15:33 +02:00
Nicolò Balzarotti 6bdc4bbcab Lot of work on HERE support. Almost working effects + GUI EQ 2017-10-11 01:15:33 +02:00
Nicolò Balzarotti ac07ddc932 Initial HERE Active Listening Device Support 2017-10-11 01:15:33 +02:00
protomors 4b4e95bfe0 NO.1 F1: Alarms support. 2017-10-10 00:41:27 +02:00
protomors 895c19cc1c NO.1 F1: Moved code for setting time into appropriate method. 2017-10-10 00:41:27 +02:00
protomors 440d1e6a81 NO.1 F1: Removed some code duplication. 2017-10-05 08:05:19 +02:00
protomors 23b66459f5 NO.1 F1: Show data fetching progress. 2017-10-05 08:05:19 +02:00
protomors bf34586a22 NO.1 F1: Check CRC before saving data. 2017-10-05 08:05:19 +02:00
Andreas Shimokawa 32e1dec0f8 bump version update changelogs
(release real 0.21.5 early because 0.21.4 had a mismatched version/tag)
2017-10-04 19:44:21 +02:00
José Rebelo 5701707e87 Mi Band 2: Change distance unit 2017-10-03 09:12:33 +02:00
Andreas Shimokawa 58c7691142 fix typo 2017-10-01 22:50:05 +02:00
Andreas Shimokawa b982d27c9a bump version, update changelog 2017-10-01 22:36:26 +02:00
protomors b819be7db6 Now setting for measurement system is sent to the device immediately after changing. 2017-10-01 20:44:35 +02:00
protomors 7b78003ba1 NO.1 F1: Display settings.
Set time format and distance units.
2017-10-01 20:44:35 +02:00
Andreas Shimokawa 6f358ff722 fix measurement system summary value not being updated 2017-09-30 23:36:20 +02:00
Andreas Shimokawa 486596b1a8 Pebble: support setting the other non-metric system crap for the pebble
(pebble health has to be activated again in app manager after changing the option)

This also moves the fake:// uri handling code from PebbleIoThread to PebbleSupport
2017-09-30 23:27:19 +02:00
Andreas Shimokawa 6d8ffad55c Call onSetConfiguration() in listener if measurement system gets changed in preferences
(And implement onSetConfiguration() for HPlus)
2017-09-30 22:45:45 +02:00
naofum 8e0688ba66 Translated using Weblate (Japanese)
Currently translated at 75.0% (310 of 413 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/ja/
2017-09-29 18:18:17 +02:00
LL 33d49dbdcc Translated using Weblate (French)
Currently translated at 100.0% (413 of 413 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/
2017-09-29 18:18:17 +02:00
Andreas Shimokawa 9f05aff11b Mi2/Bip: Various sleep fixes
- Long standing bug fixed in determinePreviousValidActivityType()
  it always got the very fist valid sample, not the nearest in the past
  (for me that was light sleep, so every timespan queried began with light sleep)
- Support type 2 (running) and 12 (wakeup(?))
- Support new firmwares (Mi2 and Bip) by masking only the last 4 bits (0xf),
  takes some ugly query hack in determinePreviousValidActivityType() since
  greenDAO does not support the & operator (even though sqlite does)
2017-09-29 17:21:16 +02:00
Andreas Shimokawa 0ffa2ce45a move up measuerement system from hplus to global preferences 2017-09-29 12:33:08 +02:00
cpfeiffer 7137387b08 Huh, travis doesn't like gradle 4.2 yet!? Trying 4.1 2017-09-28 19:17:46 +02:00
cpfeiffer ffb2f37af5 Update gradle to 4.2 2017-09-28 18:54:58 +02:00
cpfeiffer 0687564bbb Remove unused, commented out code 2017-09-28 17:31:40 +02:00
Andreas Shimokawa 7d05682d8e Amazfit Bip: whitelist firmware 0.0.9.14 2017-09-28 16:20:22 +02:00
Andreas Shimokawa 7e29234a7e bump version, update changelog 2017-09-28 16:09:48 +02:00
Andreas Shimokawa 3d09b9dc97 Amazfit Bip: Switch language on the watch automatically according to the phone locale
Supported:
- Simplified Chinese
- Traditional Chinese
- English
2017-09-28 16:04:06 +02:00
cpfeiffer 770c8a482d Bip: pass the right logger instance 2017-09-27 21:46:23 +02:00
cpfeiffer 32d5ceb78f Make update notification dismissable on error conditions
good idea, @joserebelo #821
2017-09-25 23:25:04 +02:00
cpfeiffer 976942757f Make sure we get a stacktrace in the log
(so we don't have to guess where it happened) #807
2017-09-25 23:25:04 +02:00
Andreas Shimokawa 47bdeea257 Amazfit Bip: map a few more icons 2017-09-25 22:34:05 +02:00
Andreas Shimokawa 56269c5a37 bump version, add changelog 2017-09-25 22:22:59 +02:00
Daniele Gobbetti c74ea64b70 Pebble: Address the missing remarks of #807
- Separate the getAudioPlayer method
- Replace the println calls with proper LOG statements
2017-09-25 11:09:45 +02:00
Daniele Gobbetti 4d2cc8cfcb Reconcile with changes in italian strings file
Closes #796
2017-09-25 10:59:00 +02:00
Jonas ad5dcb0836 Translated using Weblate (French)
Currently translated at 100.0% (413 of 413 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/
2017-09-25 09:58:31 +02:00
Hadrián Candela a19c3093fc Translated using Weblate (Galician)
Currently translated at 100.0% (413 of 413 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/gl/
2017-09-25 09:58:31 +02:00
Hadrián Candela a1ed51edde Translated using Weblate (Galician)
Currently translated at 97.5% (403 of 413 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/gl/
2017-09-25 09:58:31 +02:00
masakoodaa 71f10ee30e Translated using Weblate (Finnish)
Currently translated at 8.4% (35 of 413 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fi/
2017-09-25 09:58:31 +02:00
masakoodaa d0725782c9 Translated using Weblate (Finnish)
Currently translated at 0.7% (3 of 413 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fi/
2017-09-25 09:58:31 +02:00
Jasper 8d27c9baab Translated using Weblate (Dutch)
Currently translated at 23.0% (95 of 413 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/nl/
2017-09-25 09:58:31 +02:00
masakoodaa 98250855bc Added translation using Weblate (Finnish) 2017-09-25 09:58:31 +02:00
Jasper e86673fd1f Added translation using Weblate (Dutch) 2017-09-25 09:58:31 +02:00
c4ndel4 ac8524f503 Translated using Weblate (Galician)
Currently translated at 38.7% (160 of 413 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/gl/
2017-09-25 09:58:31 +02:00
c4ndel4 f230750a5c Translated using Weblate (Galician)
Currently translated at 1.2% (5 of 413 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/gl/
2017-09-25 09:58:31 +02:00
c4ndel4 178445e98c Added translation using Weblate (Galician) 2017-09-25 09:58:31 +02:00
Minori Hiraoka (미노리) d533f92748 Translated using Weblate (Korean)
Currently translated at 55.4% (229 of 413 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/ko/
2017-09-25 09:58:31 +02:00
Jonas 5150ec11a3 Translated using Weblate (Spanish)
Currently translated at 100.0% (414 of 414 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/es/
2017-09-25 09:58:31 +02:00
Jonas dc29bb7fca Translated using Weblate (French)
Currently translated at 100.0% (414 of 414 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/
2017-09-25 09:58:31 +02:00
Jonas 36cd9ba2b5 Translated using Weblate (French)
Currently translated at 100.0% (409 of 409 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/
2017-09-25 09:58:31 +02:00
Jonas a59e9d8877 Translated using Weblate (Spanish)
Currently translated at 100.0% (409 of 409 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/es/
2017-09-25 09:58:27 +02:00
cpfeiffer 3c9fab0471 Fix fw update notification being stuck at the end
Fixes #821
2017-09-25 00:03:40 +02:00
Andreas Shimokawa e28085e6af Mi Band 2/ Bip: Do not send user info if not set up 2017-09-24 23:03:11 +02:00
Andreas Shimokawa 717eb6ba58 Amazfit Bip: Whitelist fw 0.0.8.97/.98, fix detection for newer alm and cep files 2017-09-24 22:44:18 +02:00
Andreas Shimokawa 46b50515f3 Mi Band 2/Bip: set user info from user attributes 2017-09-23 00:51:57 +02:00
Andreas Shimokawa 2be84435ce Mi Band 2/Bip: implement setting user info (experimental, disabled) 2017-09-23 00:09:52 +02:00
Frank Slezak 6ec1555178 Add support for dynamic Pebble background colors (#819)
Pebble: Add support for dynamic Pebble background colors
- Add a couple additional icon types
  - Add Lighthouse (currently unused)
  - Add Transit (public transportation app)
- Tweak the colors on existing icon types
- Implement logic to grab primary (vibrant) color from app logo
  - The color will be used when displaying a notification for an app
    that does not have any configs bound to it.
- Alter NotificationType to support a color (named pebbleColor)
- Alter the Pebble notification poster to listen to the color from
  the notification
- Alter the DeviceCommunicationService to allow for color passthrough.
- Add logic to convert HEX or Integer representations of RGB888 colors
  to Pebble RGB222 format.
- make the package name retrieved lowercase.

Fixes: #815
2017-09-19 13:24:31 +02:00
Andreas Shimokawa 67584be314 Update FEATURES.md 2017-09-19 12:40:12 +02:00
Andreas Shimokawa 622a6e7679 Update README.md 2017-09-19 12:34:21 +02:00
Andreas Shimokawa 081426d2ef Remove feature list from README.md, create feature matrix instead
The README was outdated anyway, special device specific notes can be added below the table in FEATURES
2017-09-19 12:03:11 +02:00
Andreas Shimokawa f5b8bdb1c2 add missing file 2017-09-18 23:26:34 +02:00
Andreas Shimokawa 412153364e Amazfit Bip: initial support for fetching debug logs from the watch
TODO:
- fix wrong toast about start time
- properly create an API for this kind of stuff (currently uses testNewFunction())
2017-09-18 23:24:11 +02:00
cpfeiffer 35e59d0add Bip: WIP for ashimokawa 2017-09-17 22:36:49 +02:00
Andreas Shimokawa 37da178365 Amazfit Bip: log "step goal reached" event 2017-09-16 19:53:06 +02:00
Andreas Shimokawa 9471131490 Amazfit Bip: log more events coming from the watch 2017-09-16 12:33:33 +02:00
Andreas Shimokawa e5a8ca5374 Amazfit Bip: more debug output for events coming from the watch 2017-09-16 00:20:33 +02:00
Andreas Shimokawa d49db12a0d Amazfit Bip: Allow flashing of GPS CEP and Almanac, whitelist .96 fw 2017-09-15 11:44:24 +02:00
Andreas Shimokawa 3301194e75 Consolidate duplicate Mi2 / Bip icon definition
Also handle some more recently added icons
2017-09-14 10:15:14 +02:00
Kaz Wolfe 2bc0c27b90 Update Pebble Notification Icons
- Add a large number of notification icons missing from current version
- Update colors for existing icons to match Pebble colors exactly
- Add hooks for new icons

Linked issue: N/A
2017-09-14 08:04:54 +02:00
mueller-ma 92d79055e0 Remove nontranslatable string 2017-09-12 23:01:45 +02:00
mueller-ma a845f21493 Make broadcast non translatable 2017-09-12 23:01:45 +02:00
mueller-ma d7152f568f Adjust strings to meet material guidlines
Signed-off-by: mueller-ma <mueller-ma@users.noreply.github.com>
2017-09-12 23:01:45 +02:00
mueller-ma 37b5cd626e Translated using Weblate (German)
Currently translated at 99.0% (410 of 414 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/de/
2017-09-12 22:37:24 +02:00
Jonas 06c4a91eee Translated using Weblate (Spanish)
Currently translated at 100.0% (414 of 414 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/es/
2017-09-12 22:37:24 +02:00
Jonas 13b141b22f Translated using Weblate (French)
Currently translated at 100.0% (414 of 414 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/
2017-09-12 22:37:24 +02:00
Minori Hiraoka (미노리) 3e48e97ab3 Translated using Weblate (Korean)
Currently translated at 35.4% (145 of 409 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/ko/
2017-09-12 22:37:24 +02:00
naofum 0e0310efa8 Translated using Weblate (Japanese)
Currently translated at 75.0% (307 of 409 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/ja/
2017-09-12 22:37:24 +02:00
Jonas 33aa1eff90 Translated using Weblate (French)
Currently translated at 100.0% (409 of 409 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/
2017-09-12 22:37:24 +02:00
Jonas b190354ba1 Translated using Weblate (Spanish)
Currently translated at 100.0% (409 of 409 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/es/
2017-09-12 22:37:24 +02:00
mueller-ma 78494f3155 Display button action settings as disabled when feature disabled
Signed-off-by: mueller-ma <mueller-ma@users.noreply.github.com>
2017-09-12 20:24:03 +02:00
License Bot a10fc8ad4e Update license headers 2017-09-12 12:39:15 +02:00
Andreas Shimokawa acf779a8e4 Amazfit Bip: fix long messages not being displayed at all 2017-09-11 23:20:57 +02:00
Andreas Shimokawa b87d9d649d Amazfit Bip: Whitelist FW 0.0.8.88 2017-09-11 23:05:49 +02:00
Andreas Shimokawa 0b8494faee Bump version, add changelog 2017-09-11 22:35:08 +02:00
Michal Novotny 05d0625b68 Mi Band 2: Implement multiple button actions
This enables option for multiple button actions according to the "Delay after button action". This broadcast is being sent to the intent
along with button_id extra identifying how many times have the pass been done.

By pass defined number of button presses is meant.
2017-09-10 23:02:13 +02:00
Quallenauge 851e47f550 Add support for EXRIZU K8 smartband.
The device is compatible to HPLUS protocol.
2017-09-10 22:57:32 +02:00
Daniele Gobbetti 6def9dc07e Pebble: allow to translate quick actions sent to the watch
Fixes #789
2017-09-10 17:57:18 +02:00
Gabe Schrecker 6f702778f4 Support control of multiple media playback applications (#807)
* Added Lollipop only detection of the playing media package, using MediaSessionManager. Replicates funtionality of the Official Pebble client.
2017-09-10 15:17:21 +02:00
protomors b66b33239d Added database migration. 2017-09-10 14:45:09 +02:00
protomors 273c2ddbfd NO.1 F1: Support for heart rate measurement. 2017-09-10 14:45:09 +02:00
protomors 918cc75f6c NO.1 F1: fetch sleep data. 2017-09-09 18:12:39 +02:00
cpfeiffer e9a68e70b5 Make Mi2 FetchOperation reusable 2017-09-09 00:02:30 +02:00
Andreas Shimokawa 5cd00ccbb5 Fix language switching for good
Maybe
:D

Fixes #802
2017-09-07 23:26:53 +02:00
Andreas Shimokawa 1efd73af5e Update README and changelogs 2017-09-05 22:48:03 +02:00
Michal Novotny fcf9be877a Support for button actions on Mi Band 2 device (#793)
Implement button actions for MiBand 2
2017-09-05 22:37:41 +02:00
Yaron Shahrabani 25fdf50525 Translated using Weblate (Hebrew)
Currently translated at 100.0% (396 of 396 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/he/
2017-09-05 21:04:21 +02:00
Yaron Shahrabani 4ea93a2471 Translated using Weblate (Hebrew)
Currently translated at 100.0% (396 of 396 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/he/
2017-09-05 21:04:21 +02:00
Jonas 0f6a86ef8f Translated using Weblate (Spanish)
Currently translated at 100.0% (396 of 396 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/es/
2017-09-05 21:04:21 +02:00
Yaron Shahrabani 0e6b73502b Translated using Weblate (Hebrew)
Currently translated at 97.9% (388 of 396 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/he/
2017-09-05 21:04:21 +02:00
Jonas 345338b16a Translated using Weblate (French)
Currently translated at 100.0% (396 of 396 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/
2017-09-05 21:04:21 +02:00
Jonas a727b859e0 Translated using Weblate (English)
Currently translated at 100.0% (396 of 396 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/en/
2017-09-05 21:04:21 +02:00
Jonas 3f800e5fd3 Translated using Weblate (French)
Currently translated at 100.0% (396 of 396 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/
2017-09-05 21:04:21 +02:00
Jonas fac2778d05 Translated using Weblate (Spanish)
Currently translated at 100.0% (396 of 396 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/es/
2017-09-05 21:04:21 +02:00
naofum 057cf7a0d8 Translated using Weblate (Japanese)
Currently translated at 74.3% (296 of 398 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/ja/
2017-09-05 21:04:21 +02:00
Jan Lolek 62e1b1fc84 Translated using Weblate (Czech)
Currently translated at 79.3% (316 of 398 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/cs/
2017-09-05 21:04:21 +02:00
Andreas Shimokawa 1f3530c22d Translated using Weblate (German)
Currently translated at 98.7% (393 of 398 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/de/
2017-09-05 21:04:21 +02:00
Daniele a21c93359b Translated using Weblate (Italian)
Currently translated at 91.1% (361 of 396 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/it/
2017-09-05 21:04:20 +02:00
mueller-ma ea3dd08d0e Translated using Weblate (German)
Currently translated at 98.9% (392 of 396 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/de/
2017-09-05 21:03:45 +02:00
Michal Novak d6c2623ef2 Translated using Weblate (Czech)
Currently translated at 79.2% (314 of 396 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/cs/
2017-09-05 21:03:12 +02:00
mueller-ma 29ecac3557 Translated using Weblate (German)
Currently translated at 86.8% (344 of 396 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/de/
2017-09-05 21:02:42 +02:00
mueller-ma 03a8535078 Translated using Weblate (German)
Currently translated at 98.2% (389 of 396 strings)

Translation: Freeyourgadget/Gadgetbridge
Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/de/
2017-09-05 21:02:00 +02:00
cpfeiffer a93ace255b Don't crash, don't queue a transaction twice!
Fixes #794
2017-09-04 23:39:40 +02:00
cpfeiffer e44eb03698 Amazfit Bip: avoid sending weather info when not initialized 2017-09-04 23:23:51 +02:00
cpfeiffer 1ddea9268d Amazfit Bip: request GPS fw version earlier
(to avoid recreation of DeviceAttributes because fw2 is sometimes available and sometimes not.)
2017-09-04 23:19:53 +02:00
Sami Alaoui f6ce0c1a0e Add initial support for Teclast H30
Scan and connection, battery level, firmware version, date and time sync
(along with some other currently hardcoded settings), notification
support, alarm support, and some more.
2017-09-04 20:59:09 +02:00
protomors b8c5a44709 Removed interpolation of data samples. Added intensity column for activity chart. 2017-09-04 20:38:20 +02:00
protomors 22a15c631c Notify user about failed operations. 2017-09-04 20:38:20 +02:00
protomors b7c1c28e76 Split hour samples into minutes. 2017-09-04 20:38:20 +02:00
protomors ecd9964c5b NO.1 F1: Fetch steps data. 2017-09-04 20:38:20 +02:00
cpfeiffer f5934dfb3b Don't crash when no firmware version available yet 2017-09-03 23:56:30 +02:00
cpfeiffer 55bf9ef784 Some logging 2017-09-03 23:51:20 +02:00
Andreas Shimokawa cfc310692f Amazfit Bip: Fix call notification with unknown caller 2017-09-03 14:37:32 +02:00
Andreas Shimokawa 45263d08d5 bump version, update CHANGELOG
(not yet release time)
2017-09-03 14:20:40 +02:00
Andreas Shimokawa a63dc4a018 Pebble: fix taking screenshots on Android 8.0
Closes #790
2017-09-03 14:12:41 +02:00
cpfeiffer 24797c7dd7 Make AbstractGBActivity abstract :-) 2017-09-03 10:48:14 +02:00
cpfeiffer 962720145e Fix custom language being lost for newly created activities
So we do need to set the language both on change and onCreate()

For some reason, the title bar of the SettingsActivity is not updated on recreate().

Closes #787
2017-09-03 01:04:34 +02:00
Andreas Shimokawa f39d8cd2e2 Amazfit Bip: Use AlertCategory.Email for email
Somehow the custom icon for email no longer works with never firmwares
2017-09-02 23:25:53 +02:00
cpfeiffer c468e7f521 Mi Bands+Bip: when an operation finishes, unset the gatt callback on the BtLEQeue
Otherwise it will continue to receive events until another transaction is being executed.
2017-09-02 22:49:58 +02:00
cpfeiffer c91e14f644 Bip: some code deduplication 2017-09-02 21:45:56 +02:00
Andreas Shimokawa eaf7b76715 Pebble: Try to support spotify
untested
 #704
2017-09-01 23:47:03 +02:00
protomors e7fff32fb8 Send more settings to device (with hardcoded values, for now).
Now bracelet should work correctly without ever connecting to native app.
2017-09-01 22:19:33 +02:00
protomors c97136e4fe Fixed wrong parameter. Time is set in 24-hour format. 2017-09-01 22:19:33 +02:00
protomors f8473ac42d Initial notifications support and some refactoring. 2017-09-01 22:19:33 +02:00
protomors 259fc87b68 Added device icon in resources. 2017-09-01 22:19:33 +02:00
protomors f5b8fada75 Initial NO.1 F1 support.
Works: connecting, writing user data, reading firmware version and battery charge, finding device.
2017-09-01 22:19:33 +02:00
Andreas Shimokawa e839a2c6a3 Trim strings coming from DeviceInfoProfile (BLE).
On the Bip strings have trailing zeroes. Putting this in the Database results in a "BLOB"
2017-08-31 11:52:21 +02:00
Andreas Shimokawa be147913c3 Add Liberapay donation button 2017-08-29 23:56:55 +02:00
Andreas Shimokawa 32c03013ce Display fw2 as GPS for Amazfit Bip 2017-08-29 23:12:28 +02:00
Andreas Shimokawa c946ef5201 remove unused resources 2017-08-29 22:46:45 +02:00
Andreas Shimokawa 144491ea4b remove unused connectionstate_ strings (these were used in the old main activity) 2017-08-29 22:44:18 +02:00
Andreas Shimokawa 23fa37d99d Amazfit Bip: get and display gps version
Currenty this is displayed as HR (heart rate firmware)
2017-08-29 22:16:59 +02:00
AnthonyDiGirolamo f855dc5d45 Add Google Messenger as well 2017-08-28 21:34:19 +02:00
AnthonyDiGirolamo 24c9ef339b Add Pebble icon notifications for Gmail, Inbox, and Google Calendar. 2017-08-28 21:34:19 +02:00
Translation Bot d4b29418ca update translations from weblate 2017-08-27 23:43:11 +02:00
Andreas Shimokawa 4bc6e2f71d update changelogs again 2017-08-27 23:40:27 +02:00
Andreas Shimokawa a3108a4958 fix string 2017-08-27 23:37:42 +02:00
Andreas Shimokawa 455dfde63d Amazfit Bip: add instructions and warning to the firmware installation screen 2017-08-27 23:08:10 +02:00
Andreas Shimokawa 1ff8fbac55 switch speed zones and live activity tabs
(makes it easier, since live activity is not for all devices9
2017-08-27 22:00:28 +02:00
Daniele Gobbetti 612592516b Colored buttons everywhere! 2017-08-27 19:26:27 +02:00
Daniele Gobbetti f7e814431e Use material "history" icon for changelog
fixes #775
2017-08-27 18:48:13 +02:00
Daniele Gobbetti 44d2384aec Use gridlayout from support library
Add gridlayout dependency and use it instead of heavily nested linearlayouts (fragment_live_activity) or to simplify existing grid layouts.
2017-08-27 18:26:49 +02:00
Daniele Gobbetti 41feb008a7 Various style fixes
The top axis had the default color and was not visible with dark theme.
Enabled granularity of 1 to avoid fractions of steps being shown on the axis.
2017-08-27 17:26:24 +02:00
mueller-ma fe07adb041 Delete transifex config since Weblate is used 2017-08-27 11:27:16 +02:00
Andreas Shimokawa 18eb39853b remove double translation (tx and weblate were used simultaniously for a few hours) 2017-08-27 10:34:08 +02:00
Translation Bot 4e751454b4 update missing tranlations from weblate 2017-08-27 09:55:28 +02:00
Translation Bot 63e846dbf3 update translations from weblate (THANKS!) 2017-08-27 09:43:14 +02:00
Andreas Shimokawa fdcdd76b22 I hate xml 2017-08-27 01:06:35 +02:00
Andreas Shimokawa 107a03b0db prepare for release 2017-08-27 01:01:42 +02:00
Andreas Shimokawa 2eb25e7c4e Amazfit Bip: Firmware installation screen improvements
- Try to improve firmware detection (older 0.0.8.xx firmwares were not detected)
- Whitelist GPS firmware that comes with firmware 0.0.8.xx
- show actual device icon istead of hardcoded Mi Band icon
2017-08-27 00:32:15 +02:00
Andreas Shimokawa b4639b9062 Speed Zones Chart: remove unused legend code 2017-08-27 00:04:08 +02:00
Andreas Shimokawa 6fb0a977fc rename StatsChartFragment to SpeedZonesFragment 2017-08-26 23:55:11 +02:00
Vebryn 1a1eec3008 simplifying speed zones and re-enabling (#776)
* simplifying speed zones and re-enabling

* fix building error
2017-08-26 23:38:52 +02:00
Vebryn ea6457c359 fix building error 2017-08-26 23:36:59 +02:00
Vebryn a61cbddb5d simplifying speed zones and re-enabling 2017-08-26 23:35:16 +02:00
mueller-ma bde8e4c0e6 Adjust strings to meet material guidlines (#762)
* Adjust strings to meet material guidlines

https://material.io/guidelines/style/writing.html#
2017-08-26 23:10:31 +02:00
Andreas Shimokawa 88520a018c another weblate try 2017-08-26 22:43:09 +02:00
Andreas Shimokawa 3fd6590a9a add Bip to Mi Band settings string (this is a test for weblate) 2017-08-26 22:28:22 +02:00
Andreas Shimokawa dea968edf6 replace link to transifex with link to weblate 2017-08-26 21:12:01 +02:00
Andreas Shimokawa 32578d3c46 manually remove obsolte "cyrillic only" string from cs and pt-rBR translations
(transifex wanted to delete the whole string)
2017-08-26 21:07:10 +02:00
Translation Bot 1cec43bfe4 update translations from transifex (THANKS)
for the last time, we moved to weblate :)

https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/
2017-08-26 21:03:06 +02:00
Andreas Shimokawa 9ac4b923c4 bump version, update CHANGELOG (not yet release time) 2017-08-26 01:01:15 +02:00
Andreas Shimokawa 165dcf897b Amazfit Bip: fix sending weather at specific times 2017-08-26 00:42:04 +02:00
Andreas Shimokawa a5886cbb49 Revert "store UTC in weather timestamp"
It was already in UTC but I sucked

This reverts commit b9eedce13b.
2017-08-26 00:41:36 +02:00
Andreas Shimokawa 6d28b8232b Amazift Bip: fix weather on FW 0.0.8.74, support condition text 2017-08-25 23:36:14 +02:00
Daniele Gobbetti 8aebf2d9d5 Null extras bundle should not be processed.
Blind attempt to address #778
2017-08-25 22:01:21 +02:00
License Bot 15f4ce2869 Update license headers 2017-08-25 11:25:01 +02:00
Andreas Shimokawa 013e270a9c Add missing file :/ 2017-08-25 01:17:34 +02:00
Andreas Shimokawa 4bb18b9795 Amazfit Bip: enable caller name display upon initialization
This is neccessary for newer firmwares (0.0.8.74 at least)
(I assume this command also exists on Mi2, hope it is true :D)
2017-08-25 01:04:36 +02:00
Andreas Shimokawa 5f1014f553 Amazfit Bip: add .res and .gps file types to manifest 2017-08-25 00:44:26 +02:00
Andreas Shimokawa 12d9b7812f Amazfit Bip: Initial experimental firmware update support
USE AT YOUR OWN RISK:

NOTE: During update your device is named a Mi Band

1) Flash .gps (installation will take a while after transfer)
2) Flash .res
3) Flash .fw (device will reboot)
4) Gadgetbridge wont notice that update was successfull, known bug.
2017-08-25 00:21:47 +02:00
Andreas Shimokawa ebc1cedf55 damn typo 2017-08-23 23:12:44 +02:00
Andreas Shimokawa a398f33cb8 Mi Band2: do not misuse ALERT_LEVEL_ constants, use AlertCategory enum 2017-08-23 23:04:40 +02:00
Andreas Shimokawa f9e43919ae update changelogs 2017-08-22 01:04:08 +02:00
Andreas Shimokawa 460c5c9a24 Fix setting smart alarms on Mi Band
Closes #750
2017-08-22 01:01:35 +02:00
Andreas Shimokawa fd952aa5ae bump version, add changelog 2017-08-22 00:33:55 +02:00
Andreas Shimokawa b9eedce13b store UTC in weather timestamp 2017-08-22 00:26:49 +02:00
Andreas Shimokawa 4c8728c78f Amazfit Bip: Support app icons in notifications 2017-08-21 23:47:47 +02:00
Andreas Shimokawa fdcc51cb98 Amazfit Bip: Support sending notification body, not only title 2017-08-20 23:58:41 +02:00
Andreas Shimokawa a969d4b7dd update changelogs 2017-08-19 23:52:46 +02:00
Andreas Shimokawa 1eff950bde Amazfit Bip: Support some weather icons 2017-08-19 23:49:19 +02:00
Andreas Shimokawa 628efe88e5 update fastlane metadata to include Amazfit Bip 2017-08-19 21:47:01 +02:00
Daniele Gobbetti 1017561fb6 Add amazfit to the shorlist of supported vendors/devices (DE). 2017-08-19 21:32:44 +02:00
Daniele Gobbetti 8c23d6ec29 Add amazfit to the shorlist of supported vendors/devices. 2017-08-19 21:31:49 +02:00
Andreas Shimokawa 0f8638f2fd README: mention Amazfit Bip 2017-08-19 21:03:21 +02:00
Andreas Shimokawa 1f6634374d Amazfit Bip: send weather for today and tomorrow (high and lows)
TODO: convert condition codes
2017-08-19 20:58:13 +02:00
Daniele Gobbetti c05e5f15ab Add link to liberapay.com in the main drawer.
/cc #14
2017-08-18 21:51:12 +02:00
Andreas Shimokawa b19cf85a12 Amazfit Bip: support E-Mail icon and do also send notifications from unknown sources 2017-08-18 16:21:54 +02:00
Andreas Shimokawa 6f522ec5f2 update changelog 2017-08-18 14:03:09 +02:00
Daniele Gobbetti c1834ec4ea Fastlane/fdroid test - fallback images
Removed images for Italian, to check if fdroid falls back to the english images
2017-08-18 12:12:59 +02:00
Daniele Gobbetti 8cce2d1362 Pebble: allow to blacklist certain calendars
As requested in #736, this adds an entry in the settings menu that allows to blacklist certain calendars.
To avoid confusion, all the former blacklist methods and fields have been renamed to apps_blacklist. The new entries are called calendars_blacklist.
Importing the settings has not been tested with the current changes.
Closes #736

Future improvements TODO: The new setting lives in the Pebble section, i believe in the future the blackslist functionality should be centralized and put in the sidebar.
2017-08-18 10:34:42 +02:00
Daniele Gobbetti 6c38c6bb79 Reword changelog and add versioncode to xml 2017-08-17 18:18:16 +02:00
Andreas Shimokawa a6059a5ce2 Add transliteration for German
Closes #766
2017-08-16 21:41:51 +02:00
Daniele Gobbetti 91b1464824 Pebble: use iframe for clay configuration webpages
Replacing the top frame with a data-uri is forbidden since chromium-based-webview version 60.
With this commit we swap the body with an iframe when needed. Old webviews work as usual also with this approach.
No special treatment is needed for local storage as its usage is forbidden in data-uri iframes.

Fixes #758
2017-08-16 21:31:39 +02:00
Andreas Shimokawa cd5af1e66a Amazfit Bip: implement find lost device by simulating a phone call from "Gadgetbridge" 2017-08-16 20:55:20 +02:00
Translation Bot 7108dd7b88 Amazfit Bip: Implement support for rejecting calls
Taking calls does not work with recent Android versions, I guess we need to push the button in the notification :(
2017-08-15 17:24:16 +02:00
cpfeiffer 6b1ba4d161 Don't duplicate colors, use the theme #757 2017-08-14 21:59:49 +02:00
Translation Bot 5cfb108d52 update German and Hungarian from Transifex (THANKS!) 2017-08-14 17:47:23 +02:00
Andreas Shimokawa 0009ed8729 add missing icons 2017-08-14 04:26:54 +02:00
Andreas Shimokawa f0c9728775 Bump version, update CHANGELOG.md (not yet release time) 2017-08-13 22:30:40 +02:00
Translation Bot 3c9964f265 update Hungarian translation from transifex (THANKS) 2017-08-13 22:21:29 +02:00
Translation Bot 98f01e8b23 update German, Spanish, French and Japanese from transifex (THANKS) 2017-08-13 22:07:15 +02:00
Andreas Shimokawa 80dce95372 Inital Amazfit Bip support
Support is almost on Mi Band 2 level.

What does not work yet:
- flashing firmware files
- taking or rejecting phone calls
- syncing GPS tracks
- sending weather
- notification only include title, not a body
- unknown notification's text is not forwarded to the watch at all (same on Mi Band 2 #754)
2017-08-13 16:31:11 +02:00
Andreas Shimokawa 4dc53a4390 Also theme full changelog with default css (copy and paste sucks, I know) 2017-08-12 00:45:07 +02:00
Andreas Shimokawa 7302832d84 Also include DEFAULT_CSS for ChangeLog theming 2017-08-12 00:32:25 +02:00
Andreas Shimokawa b25febf0e5 Pebble: LOG stacktrace when connection fails 2017-08-12 00:18:15 +02:00
Andreas Shimokawa 9ea4b8ae43 Show dark themed changelog when dark theme is selected
Closes #757
2017-08-11 23:57:00 +02:00
mueller-ma 2f375e9a41 adjust colors (#753)
Adjusted the colors a bit to
1. match material colors
2. have a visible distinction between primary_dark and primarydark_dark
3. match the color used by the action bar and side drawer in dark theme
2017-08-10 20:42:26 +02:00
mueller-ma 3c8706cc22 Update transliteration summary
Since new chars has been added (7f5aeb6ab1) it would be better not to mention specific languages here.
2017-08-10 19:44:05 +02:00
cpfeiffer 95ce3d333e Ugly workaround for blacklist not properly persisting
Fixes #696
2017-08-01 00:10:10 +02:00
cpfeiffer 12f9386fac Also handle resetting language to default properly #733 2017-07-31 23:00:02 +02:00
cpfeiffer c1925a4e64 Properly handle and distribute language change #733
Also centralize QUIT handling in GBActivity
2017-07-31 22:49:05 +02:00
Gergely Peidl 6cb400a63c Hungarian translation updated. 2017-07-30 20:59:12 +02:00
Andreas Shimokawa c2af2dd15c Pebble: Pass booleans from Javascript Appmessage as such and convert to int16 later when sending to pebble
https://developer.pebble.com/guides/communication/using-pebblekit-js/#type-conversion
2017-07-28 23:54:29 +02:00
Daniele Gobbetti 8353026c08 Pebble: call the callbacks, do not return them 2017-07-28 18:38:03 +02:00
lazarosfs 7f5aeb6ab1 greek transliteration map 2017-07-27 23:59:06 +02:00
Andreas Shimokawa 23d12f7289 Charts: various visual improvements
Thanks @girlwithnoname
2017-07-23 17:56:32 +02:00
Andreas Shimokawa 8269b363b0 fastlane: move icon.png, run optipng on them and remove header from changelogs 2017-07-23 11:08:09 +02:00
Daniele Gobbetti fe3448f6e4 Add icon.png to the fastlane metadata and update License
- added a 512x512px version of the launcher icon to each language directory
- added the fastlane graphic resources to the LICENSE.artwork file
2017-07-23 08:53:10 +02:00
Andreas Shimokawa 7806a9a6cf remove direct link to 0.19.3, since f-droid.org now links it correctly
This reverts commit 166501e221.
2017-07-22 00:17:01 +02:00
Andreas Shimokawa aa58bf6815 update license file 2017-07-21 20:11:43 +02:00
Andreas Shimokawa 3de35a6f6a update icons (thanks @xphnx) 2017-07-21 20:09:38 +02:00
Andreas Shimokawa e5edd334c1 add fastlane changelog for 0.19.4 2017-07-21 19:59:12 +02:00
Andreas Shimokawa 30eee7ccd5 update changelog bump version 2017-07-21 19:56:49 +02:00
Andreas Shimokawa f98131ccd5 change launcher icon to non -NC licenced one (thanks @xphnx)
(added padding by fiddeling with the .svg in a text editor :O)
2017-07-21 14:18:43 +02:00
Andreas Shimokawa 0916769096 Fastlane: add full German description 2017-07-20 17:58:04 +02:00
Daniele Gobbetti caf79bb5e6 First test of fastlane metadata files for English, Italian and German:
- added title and feature graphic for all three languages
- added short and full description for English and Italian
- added changelog only for last version and only for English
2017-07-20 13:20:48 +02:00
Andreas Shimokawa 166501e221 Update README.md 2017-07-18 12:31:18 +02:00
José Rebelo f7abe2d4a3 Mi Band 2: Inactivity Warnings 2017-07-17 20:25:52 +02:00
Andreas Shimokawa 42f4200209 Change icons license (remove -NC to better comply with f-droid)
Thanks @xphnx
2017-07-15 22:30:27 +02:00
José Rebelo 34bd2ed9cc Mi Band 2: Do Not Disturb 2017-07-15 21:17:29 +02:00
cpfeiffer 01d3a3a7be Mi Band 2: set goal notification also in phase2Initialize() 2017-07-15 21:15:42 +02:00
José Rebelo ceb82f3474 Mi Band 2: Goal notification 2017-07-15 21:14:25 +02:00
cpfeiffer a43a940f0c Update gradle build tools to 2.3.3 2017-07-13 23:48:13 +02:00
cpfeiffer 18926e6bbd Merge branch 'joserebelo-mi2-display-items' 2017-07-13 23:41:33 +02:00
Carsten Pfeiffer 377e999067 Merge branch 'master' into mi2-display-items 2017-07-13 23:26:25 +02:00
José Rebelo 6c95a9fcb9 Mi Band 2: Rotate wrist to switch info 2017-07-13 23:20:11 +02:00
Daniele Gobbetti 7e6a41a773 Add a checkbox about content polices
Let the user confirm content polices have been read and understood
2017-07-13 22:26:16 +02:00
José Rebelo 2c0b105aa6
Mi Band 2: Display item settings 2017-07-12 14:59:14 +01:00
Andreas Shimokawa 23c6219cef update README.md
- Absolute link for CHANGELOG.md
- Added blog and homepage links
2017-06-30 22:36:56 +02:00
cpfeiffer 7ee3deef38 Give HPlus some more visibility 2017-06-30 21:28:20 +02:00
Andreas Shimokawa a4e35b49b2 Only show realtime chart on device supporting it 2017-06-02 21:59:46 +02:00
Translation Bot fb8f866031 disable "Speed Zones" graph
It is not ready yet and full of bugs
2017-06-02 21:42:41 +02:00
Translation Bot f9131f1c5e update translation from transifex (THANKS) 2017-06-02 21:36:20 +02:00
Andreas Shimokawa 683a074f7a update changelogs, bump version 2017-06-02 21:32:38 +02:00
João Paulo Barraca e97f4d3909 HPlus: set not worn when charging 2017-05-31 15:20:20 +01:00
freezed-or-frozen 9b5c1b91c0 modify MiBandSUpport.handleSensorData() to convert raw values in acceleration values 2017-05-30 23:11:59 +02:00
Andreas Shimokawa 05a4486277 Pebble 2/LE: try to improve pairing results by setting another unknown flag
This might help with "bad pairing" where a pebble wont connect anymore after toggling bluetooth on the watch
A workaround was to scan bluetooth before connecting after toggling bluetooth on the watch
2017-05-28 23:43:37 +02:00
Daniele Gobbetti 737578debc The good parts of "refactor notification management"
- centralize the logic for skipping unwanted notifications
- use *Compat methods wherever possible

Leaving out the problematic parts (persistent IDs and updating)
2017-05-28 18:50:41 +02:00
cpfeiffer 4e9b85999e Unregister some listeners to avoid leaking #655 2017-05-28 00:19:24 +02:00
Pavel Motyrev 3a55c67b9e Missed delimiter 2017-05-23 18:25:07 +02:00
João Paulo Barraca 4c7d6d4a10 HPlus: remove debug messages 2017-05-22 23:29:19 +01:00
João Paulo Barraca bd754b4130 HPlus: Start detecting band not worn 2017-05-22 23:19:43 +01:00
João Paulo Barraca 013cbf139a Merge branch 'master' of https://github.com/Freeyourgadget/Gadgetbridge 2017-05-22 23:14:08 +01:00
João Paulo Barraca 759b9c81a3 HPlus: Fix Unicode handling 2017-05-22 23:14:05 +01:00
cpfeiffer e279cd736f Some logging for the weird blacklist issue #696 2017-05-21 21:02:23 +02:00
cpfeiffer c79eda5507 Remove "tapString" from DeviceCoordinator 2017-05-19 22:35:37 +02:00
cpfeiffer 5e079bb480 Pull out useful code from LiveviewIOThread into BtClassicIoThread 2017-05-19 22:27:50 +02:00
João Paulo Barraca 845869e25e HPlus: Fix intensity calculation without continuous connectivity 2017-05-19 10:39:21 +01:00
cpfeiffer 1d79c9d93d Dark theme: set some text colors for the speed zone tab 2017-05-18 23:26:04 +02:00
cpfeiffer db935c650d Update gradle version to 3.5 2017-05-18 20:18:10 +02:00
cpfeiffer 03d8667827 Update gradle tools version 2017-05-18 20:14:54 +02:00
cpfeiffer 07f4d3148a Also reset the last package counter when doing a second round
See #691
2017-05-15 23:11:52 +02:00
Andreas Shimokawa b1d1e701f9 Pebble: map walk and run to TYPE_ACTIVITY instead of UNKNOWN
fixes speed zones chart being empty for pebble health
2017-05-15 22:30:07 +02:00
Andreas Shimokawa 7cce2aeb8b Cleanup Speed zones chart code
- Remove unused stuff
- rename "pie" and "sleed" (there is no sleep and no pie here :)
2017-05-15 22:19:50 +02:00
cpfeiffer e4faabeca3 Fix NPE
Closes #691
2017-05-15 19:34:33 +02:00
cpfeiffer 0e4b9a4eb8 Mi2: Keep fetch activity data until data is from today
When the fetch operation finishes successfully, double check if
the last received data is from today. If it is older, fetch again.
Closes #611
2017-05-15 00:38:26 +02:00
Vebryn 7dc9c28c74 initial version of speed zones tab (#674)
* #673 initial version of speed zones tab

* #673 fix copyrights and initial step speed length
2017-05-14 23:09:27 +02:00
Andreas Shimokawa b31a6a5db9 Pebble: Fix wrong timestamps with Morpheuz running on Firmware >=3
Fixes #689
2017-05-13 22:18:56 +02:00
Andreas Shimokawa a1690700f4 Revert "Refactor notification management"
This reverts commit cede8a0826.
2017-05-10 22:26:02 +02:00
Andreas Shimokawa 4591f07bcd Revert "Parse the EXTRA_MESSAGE bundle in the notification, if present."
This reverts commit bc28990a96.
2017-05-10 22:25:46 +02:00
Tomer Rosenfeld c3df48a25b Updated CHANGELOG.md formatting (#681) 2017-05-09 13:56:17 +02:00
Andreas Shimokawa bbbb9dd448 Pebble: do not start calendar event listener at all when calendar access is denied
Fixes #678
2017-05-09 12:32:00 +02:00
Andreas Shimokawa 805a38ae3c Merge branch 'notification-refactor' 2017-05-08 22:22:34 +02:00
Andreas Shimokawa eea1fbcca4 update changelogs, bump version 2017-05-08 22:04:24 +02:00
Translation Bot 6be1a4b7e7 updater translations from transifex 2017-05-08 21:53:43 +02:00
João Paulo Barraca d73d4b3a13 HPlus: Handles missing phone number. Fixes #675 2017-05-05 22:17:52 +01:00
Andreas Shimokawa 9f309df84d Calendar sync: Use instance id instead of event it to fix recurring events
Also parse duration string if no end time was set

If this breaks anything, revert
2017-05-04 21:52:54 +02:00
João Paulo Barraca 8a39d8b2eb HPlus: Detect Zeband Plus Unicode Support 2017-05-04 14:16:21 +01:00
João Paulo Barraca 497f9a6658 HPlus: Handle SW Version message from Zeband Plus 2017-05-04 13:47:32 +01:00
Andreas Shimokawa b475fd2dc7 Just disconnect if bluetooth gets turned off, do not quit all activities. 2017-05-01 17:33:34 +02:00
Andreas Shimokawa bdeb215fe0 Merge branch 'notification-refactor' of https://github.com/Freeyourgadget/Gadgetbridge into notification-refactor 2017-04-30 00:35:45 +02:00
Daniele Gobbetti bc28990a96 Parse the EXTRA_MESSAGE bundle in the notification, if present.
This way notifications are updating for the Conversations app.
2017-04-29 23:22:03 +02:00
Daniele Gobbetti ccfe8d5777 Merge branch 'master' into notification-refactor 2017-04-29 21:34:19 +02:00
Andreas Shimokawa 15102d525b Merge branch 'master' into notification-refactor 2017-04-29 01:27:09 +02:00
Daniele Gobbetti cede8a0826 Refactor notification management
- centralize the logic for skipping over unwanted notifications
- use *Compat methods wherever possible
- use unique and persistent ID (update notifications)
- switch to using BigText style by default (since we can now update existing notifications)
- for Pebble: delete and reinsert notification as updating is not possible
2017-04-28 18:03:19 +02:00
cpfeiffer 07c61e6bcb Revert "Revert "Enable notifications during testcases to avoid NPE starting the service""
This reverts commit 6627371f92.
2017-04-27 08:03:48 +02:00
cpfeiffer c3c5e0415d The Real Fix #666 2017-04-27 07:57:57 +02:00
cpfeiffer d8cbb18587 Revert "Ignore Test"
This reverts commit 62efd90e17.
2017-04-27 07:57:57 +02:00
cpfeiffer f1fbab7dd9 Revert "Revert "Adjust test case setup and fix failing tests""
This reverts commit b0384e90d5.
2017-04-27 07:57:57 +02:00
Andreas Shimokawa c4f8f86c00 import @Ignore 2017-04-27 07:52:12 +02:00
Andreas Shimokawa 43fc3873bb bump version, update changelog 2017-04-27 07:46:30 +02:00
Andreas Shimokawa 62efd90e17 Ignore Test 2017-04-27 07:42:20 +02:00
Andreas Shimokawa b0384e90d5 Revert "Adjust test case setup and fix failing tests"
This reverts commit d9b0d639b8.
2017-04-27 07:39:36 +02:00
Andreas Shimokawa 6627371f92 Revert "Enable notifications during testcases to avoid NPE starting the service"
This reverts commit 739b5e9c50.

Might fix #666
2017-04-27 07:37:32 +02:00
João Paulo Barraca 69d215cb99 HPlus: Improve intensity calculation based on Tanaka et al, 2001 2017-04-27 00:58:36 +01:00
João Paulo Barraca 166695f00a HPlus: Handle more frame types 2017-04-27 00:24:03 +01:00
João Paulo Barraca c9da7548ed HPlus: Improve reconnection to device 2017-04-27 00:24:03 +01:00
João Paulo Barraca 58cb73a756 HPlus: Improve transliteration 2017-04-27 00:24:03 +01:00
Daniele Gobbetti 41ecd4e7d6 Update contributors list.
Also update the self-contained script to ignore the service bots.
2017-04-26 18:15:27 +02:00
License Bot 8af9054f2d Update license headers 2017-04-26 00:14:25 +02:00
cpfeiffer 739b5e9c50 Enable notifications during testcases to avoid NPE starting the service 2017-04-25 22:51:56 +02:00
cpfeiffer 85511fb97f Updated translations from transifex (thanks!) 2017-04-25 22:03:55 +02:00
cpfeiffer b2a1805e4f Update changelog for 0.19.0 2017-04-25 22:02:57 +02:00
cpfeiffer d9b0d639b8 Adjust test case setup and fix failing tests
- add missing call super.setUp() in LoggingTest
- make use ofGBApplication's logger and db support instead
  of adding specific test things. Avoids differences between
  the local test things and the global GBApplication instances.
2017-04-25 21:55:06 +02:00
Daniele Gobbetti 534eb385f7 Pebble: manage the dictation session initialization.
Currently we inform the watch that GadgetBridge doesn't support voice input.
2017-04-24 21:32:51 +02:00
Daniele Gobbetti fae116d1bd Add Calendar related tests (WIP) 2017-04-24 13:50:53 +02:00
Andreas Shimokawa bc4503c8bf Pebble: Add option for Calender Timeline sync (default on) 2017-04-24 12:39:40 +02:00
Andreas Shimokawa 839e350d1e Bump version, update CHANGELOG 2017-04-24 12:05:04 +02:00
Translation Bot b5255f2e2a update translations from Transifex (THANKS!) 2017-04-24 12:00:31 +02:00
cpfeiffer 523055189f Some basics for testing the calendar functionality 2017-04-24 11:58:07 +02:00
Daniele Gobbetti d570d188a2 Remove the low battery notification if a normal battery level is reported. 2017-04-24 09:53:48 +02:00
cpfeiffer ed02a9781a Fix a cursor not being closed
Still won't be the cause for #655
2017-04-23 12:45:02 +02:00
Daniele Gobbetti f06298a3c8 Fix some lint errors and warnings:
- check the calling Intent action when autostarting
- replace the FAB + image with a vector drawable
2017-04-23 12:43:13 +02:00
cpfeiffer 9ed8004a69 Fix rare NPE 2017-04-22 23:56:04 +02:00
cpfeiffer 3f8620e026 Use toString() instead of risking a ClassCastException 2017-04-22 21:16:40 +02:00
cpfeiffer 2003d56190 Cleanup package blacklist handling
Didn't find a cause for #664, though.
2017-04-22 16:59:55 +02:00
cpfeiffer 36c1b5a6f2 Fix static context leak
I don't think this is the cause for #655, but it supposedly also breaks
instant run, so now it's fixed.
2017-04-21 22:34:47 +02:00
cpfeiffer 63c640f471 Port off of deprecated API 2017-04-21 22:30:29 +02:00
cpfeiffer 1e4351b03a Silence lint warning 2017-04-21 22:23:50 +02:00
João Paulo Barraca 70663af35a HPlus: Support reconnection when messages are to be sent. 2017-04-21 19:09:48 +01:00
Andreas Shimokawa 33c9752fde README: mention the reject with sms feature for pebble 2017-04-21 14:26:54 +02:00
Andreas Shimokawa edcfae3645 update README and CHANGELOG 2017-04-21 14:24:58 +02:00
Andreas Shimokawa 6dc1d76592 Pebble: recognize AOSP calendar and use calendar icon on pebble for reminders 2017-04-21 12:39:18 +02:00
Andreas Shimokawa 965ba190a6 Calendar Sync: Fix location not being passed to device code
Now location is displayed on the pebble ;)
2017-04-21 10:51:06 +02:00
cpfeiffer 696dc1f08d Attempt to fix reconnection not happening in some cases
Fixes #530
2017-04-20 23:01:32 +02:00
Andreas Shimokawa bc368a788b Pebble: fix protocol encoding bugs of timeline pin, add location 2017-04-20 21:40:06 +02:00
Andreas Shimokawa 8385d8079a Pebble: allow encodeTimeLinePin() to encode other attributes than just title and subtitle
Unfortunately I did'nt find a way yet to add a description to the calendar pin (layout 2)
2017-04-20 20:09:29 +02:00
Andreas Shimokawa 15fb71337b Calendar Sync: Fix inverted logic when comparing hashes from db
Also improve debug output
2017-04-20 20:08:16 +02:00
Andreas Shimokawa 5717379aec Pebble: change timeline layout to "calendar" for calendaar events and display end time 2017-04-20 10:40:28 +02:00
Andreas Shimokawa beb173f162 change db schema version back to 16
(master is at 15, we will merge now)
2017-04-19 21:58:13 +02:00
Andreas Shimokawa 7ee20348db Only sync Calendar and Sunrise/Sunset on devices that support it 2017-04-19 21:51:23 +02:00
Andreas Shimokawa a28d27839f Merge branch 'master' into feature-calendarsync 2017-04-19 20:19:15 +02:00
Daniele Gobbetti 3ef5f5b811 Add support for all day events and add location in the CalendarEventSpec
Further: fix the hashCode method to properly deal nulls fields.
2017-04-19 17:44:02 +02:00
Andreas Shimokawa 546b68ad2d Calendar Sync: detect changed events by hash code 2017-04-19 14:52:07 +02:00
Daniele Gobbetti 18157daf46 Ensure that the Notification listener service gets restarted if crashed.
This change adds an additional service that checks the status of the NotificationListenerService, and restarts it if it's stale/crashed.
Crashes happen mostly during development, but were reported also by users.
2017-04-19 13:23:13 +02:00
João Paulo Barraca 9decb7788b HPlus: use HR translated string 2017-04-18 10:51:49 +01:00
João Paulo Barraca 9f0d260e7a HPlus: Improve connection process (#651)
* Clean HPlus services and characteristics
* Improve connectivity
2017-04-18 10:47:28 +01:00
cpfeiffer b142add631 Pass a GBDevice instead of GBDeviceCandidate to getBondingStyle() #651 2017-04-17 23:00:16 +02:00
Andreas Shimokawa 087f5879b0 Merge branch 'feature-calendarsync' of https://github.com/Freeyourgadget/Gadgetbridge into feature-calendarsync 2017-04-17 21:11:17 +02:00
Andreas Shimokawa 35efa30c4b Calendar Sync: Only enable calendar broadcast receiver when device is initialized
This excludes it from any auto connect logic.
We are now save to sync the calendar once in contructor.
2017-04-17 21:09:29 +02:00
Daniele Gobbetti 7b50ba9572 Implement hashCode() as equals has been implemented. 2017-04-17 21:07:50 +02:00
Daniele Gobbetti 1e231e6129 Move the Calendar receiver code in the proper place. 2017-04-17 20:33:39 +02:00
Daniele Gobbetti 61690eb2cc Get notified when calendar events change instead of polling. 2017-04-17 20:26:33 +02:00
Daniele Gobbetti d9769be78d Merge branch 'master' into feature-calendarsync 2017-04-17 20:06:44 +02:00
Andreas Shimokawa 60b7a73558 Test: fix other test 2017-04-17 19:36:50 +02:00
Andreas Shimokawa a936ff0616 Tests: set device state to inizialized before assuming fw version gets written to attributes 2017-04-17 19:19:23 +02:00
Daniele Gobbetti 0cf3625304 Remove the Pebble Time 2
Pebble Time 2 is unreleased / untested, better leave it out from the supported devices list
2017-04-17 15:12:25 +02:00
Andreas Shimokawa df0e77f368 Calendar Sync: Make greendao .update() work by adding a PK id :/ 2017-04-16 21:08:49 +02:00
Andreas Shimokawa f7ca1fc76c Merge branch 'master' into feature-calendarsync 2017-04-16 19:59:23 +02:00
Andreas Shimokawa 67f035accf DBHelper: no not update device attributes in db if we call getDevice() on a disconnected device
Fixes NPE
2017-04-16 19:43:53 +02:00
Andreas Shimokawa 9970f8017f Calendar sync: save sync status to db to avoid leakage of deleted events
We now have a per device database that tracks sync states for calendar entries
We still cannot track changed calendar entries that where changed while we were disconnected
2017-04-16 19:37:43 +02:00
Daniel Hauck ccb58f0f3c Basic calendar sync using additional receiver (#654) 2017-04-16 12:34:37 +02:00
cpfeiffer 16af0724dd Replace method pair() with connectFirstTime()
Should help with #642 for hplus which did not implement pair()
2017-04-12 21:35:40 +02:00
cpfeiffer 589945f234 Use try-with-resources to ensure stream is closed on exception 2017-04-12 21:34:48 +02:00
João Paulo Barraca 6ed40a21c6 HPlus: convert text to either GB2312 or UTF-8 2017-04-12 14:29:24 +01:00
cpfeiffer c93e97f10f Shave ~600KB off the apk size with the help of a FrankenDAO
We use a patched version of greenDAO's DaoGenerator with a bugfix
and additional support for
- composite primary keys
- WITHOUT_ROWID

We used to use jitpack for including our own greendao dependency into
Gadgetbridge. Since jitpack does not know how to build a real greenDAO
release, it will simply include all artifacts, including ftl templates
etc. which we do not need at runtime at all.

We could fix this by patching greenDAO build, but we actually don't need
to do that, since we can simply use the pristine greenDAO runtime
dependency. It will happily use our custom DAOGenerator-generated
entities.
2017-04-10 21:44:00 +02:00
Andreas Shimokawa 3860c2f9c4 update changelog, bump versions 2017-04-09 22:39:50 +02:00
Translation Bot a5fdc90b6e update translations from transifex, thanks! 2017-04-09 22:35:46 +02:00
Andreas Shimokawa 810ba5419b Pebble: reenable battery level reporting, with percentage drom datalog 2017-04-09 17:05:09 +02:00
Daniele Gobbetti 5bf6251dc5 Rename some layout files as they are used for specific purposes 2017-04-09 16:03:07 +02:00
Daniele Gobbetti fe626eb11e Remove the checkboxes in the alarms cardview and simplify layout of details activity
- rename the layout file of the alarm item to better organize the files
- add a color selector for the item view, this replicates the old behavior of using the color to highlight enabled days
- remove the nested linearlayouts in the alarm details activity layout and use CheckedTextView instead
2017-04-09 16:01:48 +02:00
cpfeiffer 1a88858c6f Fix some findbugs findings 2017-04-09 01:09:43 +02:00
Daniele Gobbetti a77ff03ca5 Add filter functionality to the app blacklist activity 2017-04-09 00:21:43 +02:00
cpfeiffer bb98910e1c Update gradle to 3.4.1, pmd to 5.5.5 2017-04-08 23:59:00 +02:00
cpfeiffer 39c7c1aae3 Update robolectric to 3.3.2 2017-04-08 23:42:29 +02:00
cpfeiffer 4519f35ff1 Bump up versions of some dependencies 2017-04-08 23:38:12 +02:00
Andreas Shimokawa f658059d20 Pebble: really disable battery reporting 2017-04-08 23:19:07 +02:00
cpfeiffer 4a4a1e25df Properly format the sleep goal as a duration, not as a time
Also suppress trailing zeros, e.g. display
8h instead of 8h 0m
2017-04-08 23:16:33 +02:00
Alberto 155ce5be02 Font size bar chart (#645)
Increas font size bar chart

small fix that improves the readability of the values on bar charts
2017-04-08 22:58:58 +02:00
cpfeiffer 2feb3bed47 Reduce animation time from 350ms to 250ms
(seemed a bit laggy to me)
2017-04-08 22:52:22 +02:00
cpfeiffer c2f83fa857 Add changelog entries 2017-04-08 22:36:51 +02:00
Daniele Gobbetti 48728cbb50 Implement recyclerView in the AppBlackListActivity.
This allows to implement a search functionality (in the future)
2017-04-08 22:26:12 +02:00
Daniele Gobbetti 16cff936d3 Pebble: do not show the battery state in the control center
- the mapping voltage<->percentage is probably family dependent (or perhaps even device dependent!)
- the values are often outdated by more than one hour, this could yield to issue reports which are false positive
2017-04-08 18:46:39 +02:00
Daniele Gobbetti 018c2a971e Show the list of alarms as (material design) cards 2017-04-08 18:44:15 +02:00
Daniele Gobbetti dd5ee03932 Set the right color for the Pie charts entry labels.
Closes #647 by showing the text also on the light theme, instead of hiding it everywhere.
2017-04-08 15:50:13 +02:00
Daniele Gobbetti 2e98b1396f Do not override the android:* text colors, define our own. This fixes a crash on API 19.
Also define them in the attrs xml file and do some grouping in the colors xml.
It might be we don't need them at all, but for the time being let's keep them around.
2017-04-08 15:49:00 +02:00
Daniele Gobbetti bd833a37d4 Get rid of the v21 special style
We don't need it anymore since we are extending AppCompat.
2017-04-08 15:45:32 +02:00
Daniele Gobbetti 5a019c238a Hide the dateBar but don't set it as gone, this prevents charts from changing size when swiping. 2017-04-08 15:16:35 +02:00
Alberto de6ce1a3d7 Show all the detailed information in the CardView's overflow
Do not limit the height of the overflow list to 50dp.

It is possible that we will need to limit the maximum allowed height in the future.
2017-04-08 09:03:45 +02:00
Daniele Gobbetti 3004177f44 Update Changelog 2017-04-08 09:00:01 +02:00
Daniele Gobbetti cad777e4ce Pebble: always return the position after 10 tries. Fixes #643
This is a workaround for bugs in the javascript code of the configured watchfaces, that could fail to handle a returned failure properly and crash the webview / fill its stack.
2017-04-08 08:57:23 +02:00
Daniele Gobbetti df71d695c3 Pebble: pass an object to the ready event handlers
apparently also the ready event needs an object for some watchfaces. Reported in #643 (not the main report)
2017-04-08 08:50:18 +02:00
Daniele Gobbetti 4dbc255ad5 Pebble: return the current position to the caller of the getCurrentPosition function.
This fixes a bug introduced in d8894d315a that affects also #643.
2017-04-08 08:34:33 +02:00
cpfeiffer 60ed9ca373 Remove default intent actions out of if-condition 2017-04-07 21:55:47 +02:00
cpfeiffer 202ae53d71 Ensure that a newly discovered and initialized device will be displayed
Fixed a race condition between ACTION_DEVICE_CHANGED event handlers:
- DeviceCommunicationService added the device to the database
- DeviceManager notified the views to be refreshed

When the latter happened before the former, thew new device would not
be displayed.

=> Let DeviceManager do both.
2017-04-07 21:17:37 +02:00
cpfeiffer e1797fc9f7 When connecting for the first time, set "pair" to true 2017-04-07 00:40:33 +02:00
cpfeiffer 26ff7d67e3 Remove some now useless code 2017-04-07 00:11:25 +02:00
cpfeiffer d2053b32bf Small cleanup 2017-04-06 23:58:50 +02:00
cpfeiffer 94edaa0cc1 Update build gradle dependency to 2.3.1 2017-04-06 23:57:24 +02:00
Alberto f3edf7559d HPlus: added low battery information (#638)
HPlus: Notification of low battery

I added the notification of low battery HPlus ... specifically, I used
the HPlusConstants.DATA_STATS event on hplusSupport

I also included the status of the device in the list of
gbdevice -> getDeviceInfos
2017-04-06 23:55:29 +02:00
cpfeiffer 5b8624de71 Make pairing optional after discovery
See #642 which might be fixed by not pairing.
2017-04-06 23:47:35 +02:00
Andreas Shimokawa 3675386c13 Fix spider arms haging out of pie slices
Also make the Pie in week sleep chart look as the other pie chart in "Your Sleep"
2017-04-05 22:42:54 +02:00
Alberto 45eb14684b import export SharedPreferences (#600)
Import and export SharedPreferences

i add function for import and export SharedPreferences setting

when export or import db execute export or import SharedPreferences

for blacklist i preload HashSet
2017-04-05 00:16:17 +02:00
cpfeiffer f48729cc64 Pie Charts: some improvements wrt the labels
Still room for optimization.

Thanks to Alberto!
2017-04-05 00:08:17 +02:00
Daniele Gobbetti 8e780fa122 Add some space between the tabs and the chart legend. 2017-04-03 21:24:21 +02:00
Daniele Gobbetti 4ab39e2c00 Just applied automatic code formatting to all layout files. NO content change. 2017-04-03 21:09:35 +02:00
Daniele Gobbetti e556a65ff5 Change layout it to avoid duplicates.
It didn't cause any issue as this layout is not referenced anywhere.
2017-04-03 21:05:27 +02:00
Daniele Gobbetti 0573939c9e Lint: change marginRight to marginEnd 2017-04-03 21:03:31 +02:00
Daniele Gobbetti 96a49f0b7a Fix alignment of action icons and enable proper (vector) scaling. 2017-04-03 18:30:29 +02:00
Daniele Gobbetti d8894d315a Pebble: In the geolocation override, fail if the position is too old.
The previous logic was wrong as it was calling success every time. Further checks must be added for some watchfaces.
2017-04-02 22:34:22 +02:00
walkjivefly f0d81818b3 Swap deep and light sleep colours in charts
This partially "fixes" issue #442. The effect of the change is
purely cosmetic: deep sleep is (once again) dark blue, light sleep
is (once again) light blue.
2017-04-02 19:49:55 +02:00
Andreas Shimokawa e4c7a921ea Pebble: try to improve 70% to 60% battery level accuracy 2017-04-02 00:12:19 +02:00
Andreas Shimokawa 742615c6f4 prevent rare NPE in onNotificationRemoved() 2017-04-02 00:04:55 +02:00
cpfeiffer f2dca649a3 Update to mpandroidchart 3.0.2 2017-04-01 23:57:12 +02:00
walkjivefly f321a4bac5 Correct camel-case for mVisibleInActivity 2017-04-01 23:16:00 +02:00
Daniele Gobbetti e89ba529c3 Apply some Material design guidelines to the charts
- replace the PagerTabStrip with a TabLayout (moved to top and scrollable)
- move the date selection to the bottom
- do not update the activity title as the tab name is much more visible now
2017-04-01 17:47:54 +02:00
Daniele Gobbetti 9a0439c6e0 Apply some Material design guidelines to the app management
- replace the PagerTabStrip with a TabLayout (moved to top)
- change the row element to adhere to the guidelines wrt spacing
- move the FAB a bit and hide it when scrolling down, scroll up to reveal it again
2017-04-01 17:06:38 +02:00
Daniele Gobbetti db4e37d08b Make the drag handle less prominent (grey) 2017-04-01 15:45:30 +02:00
Andreas Shimokawa 173b4fbbe6 Add legend ans labels to weekly sleep charts
Also remove the "missing sleep" gray entry to avoid confusion
2017-03-31 23:42:25 +02:00
Daniele Gobbetti 8fccbe3b69 Pebble: implement battery display in control center
- extract the millivolts reading from the analytics datalog message and map to percentage
- mapping is manually made and is possibly wrong, but the values are commented
- the values are sent once per hour and are delayed, this might make the reading really inaccurate on pebble time round watches
2017-03-31 18:23:02 +02:00
Daniele Gobbetti f80215b37a Use standard recyclerview for app reordering
- allow dragging by using a drag handler (as per best practice)
- remove the custom draglistview dependency
- update to the latest android support libraries
2017-03-31 18:17:53 +02:00
Andreas Shimokawa 562049296c Pebble: fix datalogging
Was broken after 0.18.4
2017-03-30 23:01:40 +02:00
João Paulo Barraca ab3f6c0bbf Fixes #629 2017-03-30 14:45:02 +01:00
Daniele Gobbetti 447885033b Add links to the wiki sections next to supported devices line 2017-03-29 08:59:53 +02:00
Daniele Gobbetti eae119f9df Add some preliminary checks/links to the issue template 2017-03-29 08:53:13 +02:00
Andreas Shimokawa b25bc66485 Allow Datalog handlers to return GBDeviceEvent[] 2017-03-28 09:51:06 +02:00
Andreas Shimokawa 07272e5a68 update changelog, bump version to 0.18.4 2017-03-27 22:15:03 +02:00
Daniele Gobbetti 46501be249 Enlarge the "hit box" of each icon (replace some margin pixels with padding).
Fixes #615
2017-03-27 13:56:03 +02:00
Daniele Gobbetti 1813ec9378 Forbid landscape orientation on AlarmDetails activity.
Landscape mode triggers a bug in android DatePicker on devices with small screens.
Fixes #612
2017-03-26 20:29:57 +02:00
Daniele Gobbetti d550defcb3 Do not save an alarm as "smart" if the device does not support it (#612)
Opening the activity when a device that does not support smart alarms is connected hides the "smart alarm" toggle. This is now reflected also on the saved data.
This solution is not ideal in case of multiple devices but as long as #577 is not solved its the best we can do.
2017-03-26 17:57:03 +02:00
João Paulo Barraca 11b48e7a1a Set HPlus Sleep Intensity to 10% 2017-03-24 20:52:14 +00:00
João Paulo Barraca 75089d7cb4 Add HPlus and LiveView icons to level list 2017-03-24 18:10:05 +00:00
cpfeiffer da9742fd67 Mi1: Attempt to recognize Mi1 model with hwVersion = 8
Closes #364
2017-03-20 23:25:42 +01:00
cpfeiffer 14552a1a80 Disable live activity tracking when activity is paused
Fixes #496
2017-03-20 23:01:28 +01:00
cpfeiffer b97674ba85 Mi2: Display realtime steps in Live Activity #428
Thanks for the hint!
2017-03-20 23:01:28 +01:00
Andreas Shimokawa 7c63f92aaa Remove obsolte and unused code related to old and unsupported activity database 2017-03-20 22:27:17 +01:00
Translation Bot ad82a75312 update translations from transifex (thanks!) 2017-03-19 21:34:57 +01:00
Andreas Shimokawa abd79ef5c9 0.18.3 2017-03-18 17:22:40 +01:00
Andreas Shimokawa 5d042cf3c8 Fix weekly charts to display the same value for every day Android < 7
Closes #604
2017-03-18 17:13:35 +01:00
cpfeiffer cc159cf80f 0.18.2 2017-03-18 11:46:08 +01:00
cpfeiffer f1d07c83f6 Fix NPE #603 2017-03-18 11:41:47 +01:00
Andreas Shimokawa b2886b81c9 bump version, update changelog 2017-03-17 22:52:36 +01:00
Andreas Shimokawa fe07e09d41 Fix firmware installation on Pebble Time Round
Closes #602
2017-03-17 22:34:50 +01:00
Andreas Shimokawa 9eade33d72 Start VibrationActivity when using "find device" button with Vibratissimo
(The activity was inaccessible)
2017-03-16 17:38:13 +01:00
License Bot 6a842c52fa Update license header in all java files. 2017-03-16 17:36:15 +01:00
Andreas Shimokawa 7a6b0ed2b0 support material fork of K9 2017-03-16 17:24:15 +01:00
Andreas Shimokawa 408dd9c26f Do not try to request k9 permissions in CCv2, we dont need it 2017-03-16 17:20:18 +01:00
cpfeiffer f4e955dbe0 Updated changelog for 0.18.0 2017-03-15 23:42:30 +01:00
Translation Bot c1af01a155 update translations from transifex (thanks!) 2017-03-15 21:26:05 +01:00
cpfeiffer d408be5ec8 Mi2: make text/icon notifications confiurable and version dependent 2017-03-15 00:26:39 +01:00
cpfeiffer cf35e84feb Mi2: add hint about font installation for text notifications 2017-03-14 23:54:56 +01:00
cpfeiffer 5d96df3508 Mi2: add hint about intermediate firmware 1.0.0.53 2017-03-14 23:45:30 +01:00
cpfeiffer 2d60beea1f Mi2: added some more tested firmware/font versions 2017-03-14 22:59:48 +01:00
cpfeiffer e62a860ee6 Avoid potential NPE when neither name nor number are available 2017-03-14 22:03:30 +01:00
Alberto 6989ca9db3 Add privacy mode that hides the phone number (#588) 2017-03-14 21:45:36 +01:00
cpfeiffer 17ecee0cab Mi2: initial support for text notifications and icons
See #560
2017-03-14 00:45:54 +01:00
cpfeiffer 8fc6dfeca7 Improved AlertNotificationProfile
AlertLevel, AlertCategory, Control Point
2017-03-14 00:45:54 +01:00
cpfeiffer 4b230412b6 Some utility methods + tests 2017-03-14 00:45:54 +01:00
Andreas Shimokawa a6bba1b094 In CCv2 allow to disconnect with long press in any state expect "not connected" 2017-03-13 22:27:59 +01:00
Daniele Gobbetti 5008f08272 Revert "Use constraintlayout for the cardview and few improvements."
This partially reverts commit ecd2c166c2 because the ConstraintLayout dependency it creates problems in travis and in f-droid build system. #thanksgoogle #wecanthavenicethings :(
2017-03-12 09:06:58 +01:00
Andreas Shimokawa 2c1923dd96 change icon for "find lost device" action, add small padding to app manager icon 2017-03-11 23:37:19 +01:00
Translation Bot 6020b591d7 update Italian from transifex 2017-03-11 21:57:38 +01:00
Andreas Shimokawa 99bc922d13 update draglistview to v1.2.9 2017-03-11 21:52:42 +01:00
Daniele Gobbetti 0ac77fc0a4 Remove the legacy ControlCenter and its usages. Add New GUI to the changelog. 2017-03-11 19:49:57 +01:00
Daniele Gobbetti 183d89dc47 Show the full changelog when selecting the Changelog entry in the navigation drawer.
Show a line separator between groups of icons in the navigation drawer.
2017-03-11 17:10:51 +01:00
Daniele Gobbetti ecd2c166c2 Use constraintlayout for the cardview and few improvements.
Icons are now bigger.
"find device" is in the icon row
2017-03-11 16:50:12 +01:00
Daniele Gobbetti 2c152e8447 Override textColorPrimary and not textColor as it conflicts with support libraries (e.g. snackbar text).
Further, use textColorPrimary in the graphs instead of textColor.
2017-03-11 16:48:55 +01:00
Daniele Gobbetti 68608f8582 Fix merge conflicts (from 3abbe12b53) and update the support libraries. Add constraint layout library. 2017-03-11 16:45:39 +01:00
Daniele Gobbetti 8117caf73c Merge branch 'master' into new_GUI 2017-03-11 16:44:16 +01:00
Andreas Shimokawa fe870ebc77 Move step goal generic, show proper sleep goal in weekly sleep goal
It is now also accessible via "About You" but still remains in the Mi Band Settings
(because it has to be setup initially by a "wizard")
2017-03-11 11:34:03 +01:00
Andreas Shimokawa e6928202c5 update changelog 2017-03-11 10:52:42 +01:00
Translation Bot aa60acbf07 update translations from transifex (THANKS) 2017-03-11 10:48:54 +01:00
Daniele Gobbetti c23b938a9a Update changelog 2017-03-11 10:32:04 +01:00
Daniele Gobbetti a1af4a4599 Add classification for Squeaky Mail as mail app. Fixes #589 2017-03-11 10:23:57 +01:00
Translation Bot 05a28cc580 Pebble: Fix screenshots for Android N
This was a regression only in master since we switched to SDK 25

This commit also fixes all other warnings with AbstractDeviceSupport.java
2017-03-10 23:10:40 +01:00
License Bot e392fbfd80 Add license header to all java files. 2017-03-10 14:53:19 +01:00
Daniele Gobbetti a566a6656c Fix line endings (no content changes) 2017-03-10 13:43:47 +01:00
cpfeiffer 6282597790 Handle BT issues with discovered characteristics
It occurred to me that onServicesDiscovered() was called multiple times
whenever I connected to my Mi Band 2. Either the band actually sent the
characteristics in multiple chunks or Android itself notified multiple
times.

So we gracefully handle this by
- updating the list of characteristics
- *not* initializing the device again when it *is* already initialized
or at least initializing.
2017-03-09 22:49:13 +01:00
Translation Bot 3abbe12b53 Merge branch 'master' into new_GUI 2017-03-08 21:42:52 +01:00
Andreas Shimokawa f070ce5ce7 fix copy and paste error in xml changelog 2017-03-08 13:49:28 +01:00
Andreas Shimokawa 410fc0e8dc update changelogs 2017-03-08 12:51:14 +01:00
cpfeiffer 9411f80440 Mi2: support for updating firmware fonts (*.ft, *.ft.en)
This is related to #560, but alas is not sufficient for enabling text
notifications.
2017-03-07 23:26:41 +01:00
cpfeiffer 2b17d7fb14 More cleanup 2017-03-07 00:06:35 +01:00
cpfeiffer 31e0e9a5f7 Mi2: More internal cleanup from initial Mi1 copy&paste 2017-03-05 21:45:39 +01:00
cpfeiffer f6bee00582 Mi2: some internal cleanups/renamings 2017-03-05 21:27:47 +01:00
cpfeiffer 0b45fe63f0 Fix up the testcases
Please check if transliteration of Hebrew in LnaguageUtilsTest is
correct. It works just fine if you follow the mapping in LanguageUtils.

Test all transliteration in LanguageUtils only, the test in
DeviceCommunicationServiceTest does not need to be done for every
language.

Also use assertEquals(expected, value) instead of assertTrue(expected.equals(value));
2017-03-05 19:44:31 +01:00
cpfeiffer 09d4f81ce8 Update dependencies 2017-03-05 19:44:16 +01:00
cpfeiffer 4ecd4b6896 Add hint about privacy guard crashing your phone during discovery 2017-03-05 19:18:40 +01:00
cpfeiffer 94744677c9 Improve discovery hint re Privacy Guard crash 2017-03-05 16:30:31 +01:00
cpfeiffer c56b655b48 Mi2: send text notification for mi2 only, not mi1a 2017-03-05 10:43:19 +01:00
cpfeiffer 88b35c6eec Mi2: rename some constants + add two 2017-03-05 10:43:19 +01:00
Andreas Shimokawa 4a3eb6e8de fix obvious copy&paste error in tests 2017-03-04 22:46:41 +01:00
Yaron Shahrabani 858eaa6690 Added Hebrew transliteration and tests (#571) 2017-03-04 22:08:24 +01:00
cpfeiffer 58e2538c4e Discovery: handle the case where a device is already bonded 2017-03-04 16:03:36 +01:00
cpfeiffer 09967b2006 Mi2: follow 12h/24h system configuration
=> Remove custom preference option, #573
2017-03-03 22:32:54 +01:00
cpfeiffer 82ea5702c5 Mi2: configurable time format (12h/24h)
Fixes #573
2017-03-03 22:14:28 +01:00
cpfeiffer 72801af0e7 Added Hebrew translation, thanks! 2017-03-03 20:48:13 +01:00
cpfeiffer 4419200624 Mi2: Initial support for textual notifications #560 2017-03-03 20:12:43 +01:00
Andreas Shimokawa aa6e9608bd travis trial and error 2017-03-03 17:40:52 +01:00
Andreas Shimokawa a90e0074fc Change weekly sleep charts to display sleep from 12:00-12:00 instead of 0:00-0:00 2017-03-03 17:33:00 +01:00
Andreas Shimokawa a0bb0c973c bump compilesdk and support libraries to v25 2017-03-03 14:35:44 +01:00
Andreas Shimokawa 216d35aeee bump version to 0.18.0
(not yet release time)
2017-03-03 14:23:51 +01:00
Andreas Shimokawa 9b2f47d10a Improvements to week sleep chart
Time to close #557
2017-03-03 14:21:59 +01:00
Andreas Shimokawa 1efdfb757c update .travis.yml also 2017-03-03 09:48:08 +01:00
Andreas Shimokawa f97a53a89f bump buildtools version to 25.0.2
25.0.0 was suggested fist - :/
2017-03-03 09:33:57 +01:00
Andreas Shimokawa 03181202d1 update buildtoolsversion to 25.0.0 2017-03-03 09:32:43 +01:00
Andreas Shimokawa 55019579ef update translations from Transifex (thanks!) 2017-03-03 09:31:19 +01:00
Andreas Shimokawa 914143960d update grade for AndroidStudio 2.3 2017-03-03 09:27:56 +01:00
Andreas Shimokawa 7a3b2899e7 trial and error 2017-03-03 08:58:11 +01:00
Andreas Shimokawa cbebd845cf rename pt_BR to pt_rBR, fixes #569 2017-03-01 17:29:32 +01:00
cpfeiffer 7fea11d752 Changelog: New translations for 0.18 and icons during discovery 2017-02-28 20:47:22 +01:00
cpfeiffer c619f17637 Updated translation from transifex (thanks) 2017-02-28 20:38:48 +01:00
cpfeiffer 21f2fed7e8 Add Portuguese translations from Transifex (thanks!) 2017-02-28 20:32:37 +01:00
Daniele Gobbetti ca73d0c2d4 Add mapping to weather icons 2017-02-27 21:45:10 +01:00
cpfeiffer 19b0e5e801 Centralize icons for devices 2017-02-26 22:46:49 +01:00
Andreas Shimokawa ac1875eea0 Charts: In "Sleep a week" chart display light and deep sleep as stacked bars 2017-02-26 21:41:27 +01:00
Daniele Gobbetti 176cf79cc1 Merge branch 'master' into new_GUI
# Conflicts:
#	app/build.gradle
2017-02-26 16:55:54 +01:00
Andreas Shimokawa 8b39ef3a52 Speedup for charts by caching aggregated sleep amounts and steps for maximum 32 days 2017-02-26 00:40:50 +01:00
Andreas Shimokawa aac9827e63 remove OnboardingActivity from manifest (its gone) 2017-02-25 17:58:19 +01:00
Daniele Gobbetti e7846f4754 Pebble: override the native navigator.geolocation GetCurrentPosition
The native method requires FINE location permission, and we don't have it. Further we should handle the runtime permission request.
2017-02-25 13:31:48 +01:00
Daniele Gobbetti 2eb43fa740 Pebble: Fix for week sleep chart amounts (the colors are still wrong) 2017-02-25 12:52:24 +01:00
Andreas Shimokawa 08080b02bb Drop support for legacy (pre 0.12.0) database 2017-02-23 22:44:44 +01:00
cpfeiffer 49e1b55ad8 Format center-text for week-sleep-pie 2017-02-23 21:15:57 +01:00
Andreas Shimokawa 437ec6c9b7 Minor improvements to the WIP week sleep chart 2017-02-23 08:50:31 +01:00
cpfeiffer 6834f36ec7 Updated contributors list 2017-02-21 22:52:06 +01:00
cpfeiffer 337bfa1938 Handle button presses and log them
See #556
2017-02-21 21:41:21 +01:00
Andreas Shimokawa e9cb5fd374 WIP sleep in a week chart
Displays minutes which is confusing
Only displays deeps sleep (no idea why)
Is green (which is also confusing)
2017-02-21 16:20:42 +01:00
Andreas Shimokawa db58b32b6f Update two german strings 2017-02-20 22:23:44 +01:00
Andreas Shimokawa 24794c46b1 update translations from transifex (THANKS) 2017-02-20 22:20:22 +01:00
Andreas Shimokawa c23e496db6 bump version, update changelog 2017-02-20 22:19:16 +01:00
Andreas Shimokawa 2dbda6138b Pebble: some cleanups and simplifications for datalogging via PebbleKit 2017-02-20 22:09:00 +01:00
Andreas Shimokawa ad9cfae6f9 Pebble: Pass datalog creation timestamp to PebbleKit, properly announce PebbleKit datalogging support 2017-02-20 08:47:42 +01:00
Andreas Shimokawa 946ed5f000 Pebble: First shot at implementing dataloggin for PebbleKit apps
Closes #497
Could help #316
2017-02-19 23:02:55 +01:00
cpfeiffer e5d09b9fa2 Automatically start the service on boot (can be turned off)
Fixes #9
2017-02-17 23:24:44 +01:00
Avamander 23f2dd35d4 Extract music shuffle and repeat states and set the song progress to auto-update. (#554) 2017-02-17 09:01:37 +01:00
Andreas Shimokawa a26563d6c7 Pebble: also acknowledge PebbleKit intents with transaction_id -1
I don't understand why this should be necessary but for some 3rd party apps it helps (#509)
2017-02-13 22:27:37 +01:00
Andreas Shimokawa 1d1edd41d7 Pebble 2/LE: remove a sleep which might be no longer necessary
Will speedup data transfer
2017-02-12 23:13:24 +01:00
Andreas Shimokawa b31dd9b2fa translate some strings to German 2017-02-11 22:52:58 +01:00
Andreas Shimokawa c851f73265 update CHANGELOG 2017-02-11 22:42:39 +01:00
Andreas Shimokawa 3936a7d8a0 update all languages from transifex (THANKS)
French changes merged by a PR are overwritten though this. We didn't have a solution from that - so I will just rely on transifex - sorry.
2017-02-11 22:34:04 +01:00
Avamander fea31924ba Music data handling improvements (#550)
* Fixed extracting the track length.

* Added current track and total track count.

* Few small changes to make sure everything gets updated properly.

* Remove unnecessary includes.
2017-02-11 09:49:01 +01:00
Andreas Shimokawa 5dfd40062f fix previous commit 2017-02-10 23:16:36 +01:00
Andreas Shimokawa f956d94181 Pebble: make sure to not display "open on phone" and "dismiss" when the source of a notification was our AlarmClockReceiver 2017-02-10 23:11:21 +01:00
Andreas Shimokawa ee28ccd4fe Pebble: add a dev option to always and immediately ACK PebbleKit messages to the watch
Might help #509
2017-02-10 23:07:36 +01:00
cpfeiffer 0042ffc514 Set the notification ID on alarm start 2017-02-10 22:52:55 +01:00
cpfeiffer 89bf63d540 Implement hashCode() when you implement equals()! 2017-02-10 00:32:03 +01:00
Andreas Shimokawa f35e3e460d remove K9 receiver as is works better with generic notifications 2017-02-09 17:35:46 +01:00
Andreas Shimokawa c0076b20d3 fix copy and pasta error 2017-02-09 17:24:07 +01:00
Andreas Shimokawa 083b8db1ec update Japanese and Spanish from Transifex (THANKS!) 2017-02-09 17:20:29 +01:00
Andreas Shimokawa 2b7162055d bump version, add changelog 2017-02-09 17:18:33 +01:00
Andreas Shimokawa 5bb1995eb9 Pebble: fix privacy mode title being "null" in some cases 2017-02-09 17:10:39 +01:00
Andreas Shimokawa 436a7ddb24 Pebble: fix alarm notifications only working one
id -1 means undefined, everything else is liked to a real android notification
2017-02-09 15:07:00 +01:00
cpfeiffer 4f0674d038 Support for alarm clock notifications for Mi1 + Mi2 #538
No support for Pebble and HPlus for now.

Atm relies on the CM deskclock alarm, which nicely broadcasts
events about the current alarm. See
https://github.com/CyanogenMod/android_packages_apps_DeskClock.git
2017-02-07 23:49:10 +01:00
Andreas Shimokawa e2b3394900 made caller privacy pebble setting generic 2017-02-07 10:15:23 +01:00
Andreas Shimokawa b6852308b7 Merge pull request #546 from jpbarraca/hplus-preferences
HPlus: add device specific preferences and icon
2017-02-06 18:50:40 +01:00
Andreas Shimokawa 32a326c24b Merge pull request #543 from jpbarraca/hplus-alarm-clear
HPlus: Clear Alarms
2017-02-06 18:49:28 +01:00
João Paulo Barraca 475426c0ed Merge branch 'master' into hplus-preferences 2017-02-06 01:39:45 +00:00
João Paulo Barraca a3cc84c01d HPlus: add device specific preferences and icon 2017-02-06 01:33:15 +00:00
João Paulo Barraca bf8ae5d5af HPlus: Add constant ARG_ALARM_DISABLE 2017-02-05 23:55:48 +00:00
João Paulo Barraca 644c06df68 HPlus: Clear alarms if no alarm is enabled 2017-02-05 23:52:36 +00:00
Andreas Shimokawa 030edef033 Merge pull request #541 from ivanovlev/master
Transliteration: capitalized just the first letter in the replacement
2017-02-05 22:59:03 +01:00
Andreas Shimokawa b3cddebdbb Pebble: ensure a better error message if someone tries to install a FW 1.x pbw 2017-02-05 22:50:42 +01:00
Daniele Gobbetti b7bad268c2 Pebble: support weather for Obsidian
Ref: #482
2017-02-05 17:21:04 +01:00
Daniele Gobbetti dccd6c1b06 Pebble: implement privacy modes
The user can choose whether to completely hide the notification text or push it off-screen. This also effects the incoming call notification.
Fixes #370
2017-02-05 16:37:59 +01:00
Daniele Gobbetti b894c01822 Make the media notification receiver more robust.
Sometimes the media notification does not contain the expected components, hence the code covered by the try/catch has been adjusted. This was reported in #533 for VLC.
In the future the whole media handling will probably be refactored.
2017-02-05 15:13:26 +01:00
ivanovlev fd61dc602f Transliteration: capitalized just the first letter in the replacement text 2017-02-04 09:53:07 +03:00
Andreas Shimokawa cc917e97a6 Merge pull request #540 from jpbarraca/hplus-fix-time-sync
HPlus: Fix time sync and Time format (12/24)
2017-02-03 22:43:55 +01:00
João Paulo Barraca 006a23dfe8 HPlus: Fix time sync and Time format (12/24) 2017-02-03 19:30:59 +00:00
Andreas Shimokawa 22cf74bbd1 Merge pull request #532 from jpbarraca/alarm
HPlus: Working alarms and small cleanup
2017-02-03 14:02:29 +01:00
Daniele Gobbetti 3fcf4938b9 Changelog for Music receiver refactoring 2017-02-01 23:10:23 +01:00
Daniele Gobbetti e08a900978 Refactor the MusicPlaybackReceiver and related files
Add actions to the filter (this should help with #536)
Add "copy" constructors to MusicSpec and MusicStateSpec, and use those when receiving an updated intent, this way partial updates do not disrupt the local information.
Iterate over incoming extra keys, explicitly check the incoming type and use only known type. This could help with #533

Possible problem: this code iterates over every key of the incoming bundle.
2017-02-01 22:55:40 +01:00
João Paulo Barraca f79e8f8833 HPlus: Working alarms and small cleanup 2017-01-30 23:37:47 +00:00
cpfeiffer d030ad9400 Ups, remove accidental import from C&P 2017-01-29 23:06:40 +01:00
cpfeiffer b157f84b83 Log connection attempt when BT is turned on 2017-01-29 22:56:56 +01:00
cpfeiffer 2ae4497261 Mi2: the only reason I can see for detecting MiBandSupport for Mi2
Fixes #529, hopefully
2017-01-28 23:25:15 +01:00
cpfeiffer ec6a8b6743 MI: some more logging 2017-01-28 23:25:15 +01:00
cpfeiffer 6c16b4fb15 Updated initial array list size to reflect reality 2017-01-28 23:25:15 +01:00
cpfeiffer 4c48b473ac Show device type in GBDeviceCandidate.toString() 2017-01-28 23:25:15 +01:00
Carsten Pfeiffer 42031cb50f Merge pull request #527 from ivanovlev/master
Fix issue #522 : Transliterate Caller Name
2017-01-28 21:50:08 +01:00
ivanovlev 2d3907b0f0 Fix issue #522 : Transliterate Caller Name 2017-01-27 23:16:19 +03:00
cpfeiffer 13af1c1e11 Ignore Gadgetbridge's own notifications
Fixes #411
2017-01-27 00:23:45 +01:00
cpfeiffer f9779d9695 Improve some messages 2017-01-26 23:22:57 +01:00
cpfeiffer ba7d13fa5d Disable smart alarms for Mi2
Closes #471
2017-01-26 21:09:49 +01:00
Andreas Shimokawa 298e2a9955 Merge pull request #524 from jpbarraca/Makibes-F68-refactor
HPlus: Change Makibes F68 device type
2017-01-26 17:24:12 +01:00
Andreas Shimokawa f81ff8591b Pebble: add a FAB in App Manager which launches a file manager to chose a file
This is similar to #247 but simpler and using a FAB, also it explicitly targets our Activity instead of allowing to open a video in a video player which using this feature
Also suggested in #520
2017-01-26 17:23:28 +01:00
João Paulo Barraca d7db6559d8 HPlus: Change Makibes F68 device type 2017-01-26 16:04:33 +00:00
Andreas Shimokawa e19ea26478 Merge pull request #513 from ivanovlev/master
Simplification of transliteration integration
2017-01-26 16:13:54 +01:00
Andreas Shimokawa 896eb19b3e Merge pull request #523 from jpbarraca/Makibes-F68
HPlus: Support for Makibes F68
2017-01-26 16:07:26 +01:00
Andreas Shimokawa b0ac785066 release time 2017-01-26 15:23:43 +01:00
Andreas Shimokawa cfa08d4fc4 fix changelog 2017-01-26 14:38:38 +01:00
João Paulo Barraca b3e1cbf55e HPlus: Support of Makibes F68 and small fixes to HPlus devices 2017-01-26 13:02:58 +00:00
cpfeiffer 5d3028c123 Mi1+2: Updated changelog 2017-01-26 00:17:50 +01:00
cpfeiffer ac68bfe351 Mi Band 1+2: backend implementation of making BT pairing optional
- cleaned up the DeviceService.connect() variants
- discovery: pass the device candidate around instead of the mac address

Attempts to fix #512, #514, #518
2017-01-26 00:16:38 +01:00
cpfeiffer b8b2d8830f Fix parceling GBDeviceCandidate 2017-01-26 00:16:38 +01:00
cpfeiffer 4c26c2933b Mi Band 1+2: make BT pairing optional
(Attemts to fix #512, #514, #518)
2017-01-26 00:16:38 +01:00
cpfeiffer d103d09fcf Mi Band: just a method rename 2017-01-26 00:16:38 +01:00
Andreas Shimokawa 083cbdbfbe update Spanish and Japanese from transifex (THANKS) 2017-01-25 22:17:35 +01:00
Andreas Shimokawa 2b632d8b39 bump version, update changelog 2017-01-25 22:15:21 +01:00
Andreas Shimokawa 25433ef6bc Pebble: do not display a toast when watchapp configuration could not be found during initialization of appmessage handler
Unfortunately all users without TimeStyle installed got an error in Gadgetbridge 0.17.2
2017-01-24 23:12:36 +01:00
Andreas Shimokawa 4f45ad660d Pebble: refactor PebbleKit stuff into its own class 2017-01-24 22:56:09 +01:00
ivanovlev 09539fd9bf Add transliteration test 2017-01-25 00:04:05 +03:00
ivanovlev 06295abcb6 Simplification of transliteration integration 2017-01-24 21:04:06 +03:00
Andreas Shimokawa a451b5367b Pebble: dynamic key support for Square handler 2017-01-24 19:02:45 +01:00
Andreas Shimokawa 712ce1aa8b Pebble: dynamic keys support for healthify 2017-01-24 18:50:43 +01:00
Andreas Shimokawa 3233432ee1 Pebble: simplify AppMessageHandler 2017-01-24 18:38:26 +01:00
Andreas Shimokawa 3dd058cf81 Merge pull request #517 from jpbarraca/hplus-fix-3
HPlus: Improve display of new messages and phone calls
2017-01-24 12:05:02 +01:00
Andreas Shimokawa fb7db523c7 Pebble: dynamic appKey suppoort for Morpheuz 2017-01-24 11:58:13 +01:00
João Paulo Barraca b4a4b3916a HPlus: Remove LanguageUtils transliterate from HPlusSupport 2017-01-24 10:39:24 +00:00
Andreas Shimokawa 746eeda777 Pebble: use dynamic appkeys for TrekVolle handler 2017-01-24 11:07:49 +01:00
Andreas Shimokawa 8027b8ac96 Pebble: fix potential crash when encoding appmessages with null values 2017-01-24 11:07:00 +01:00
Andreas Shimokawa 378d285b1a Merge pull request #515 from jpbarraca/hplus-fix-2
HPlus: Fix bug related to steps and heart rate
2017-01-24 08:21:07 +01:00
João Paulo Barraca 1f083041b9 HPlus: Improve display of new messages and phone calls 2017-01-24 01:44:30 +00:00
João Paulo Barraca c4a0c60b8c HPlus: Fix bug related to steps and heart rate 2017-01-22 23:33:30 +00:00
Andreas Shimokawa 019da98dfa escape ' in strings.xml 2017-01-22 22:40:24 +01:00
Andreas Shimokawa c39318af05 CHANGELOG, bump version, improve strings about transliteration 2017-01-22 22:30:40 +01:00
Daniele Gobbetti 373e96ca30 Fix formatting of issue template, add GB version
h/t @IzzySoft
2017-01-22 16:53:26 +01:00
Carsten Pfeiffer 5b116aae93 Merge pull request #511 from 6arms1leg/documentation
Added "known issues" item for Mi Band devices about firmware version …
2017-01-20 22:58:30 +01:00
Daniele Gobbetti a7a37fd9c8 Pebble: add a method to use the JSON keys instead of hardcoding the key ID.
This needs parsing the json but it is only done once. So far only Timestyle apphandler uses the new approach and this fi_xes the issue reported here https://github.com/Freeyourgadget/Gadgetbridge/issues/482#issuecomment-273757492
Fixes also a potential crash when the message for pebble contained a null key in one of the Pairs
2017-01-20 19:17:00 +01:00
6arms1leg befedaf7d2 Added "known issues" item for Mi Band devices about firmware version related issues. #508 2017-01-20 15:33:27 +01:00
Carsten Pfeiffer 31ccaf361b Merge pull request #500 from ivanovlev/master
Transliterate unsupported Russian characters into English
2017-01-19 23:42:56 +01:00
ivanovlev c13725911f Transliteration off by default, if setting not exist 2017-01-19 08:09:36 +03:00
Andreas Shimokawa cf45c665a5 bump version update CHANGELOG 2017-01-18 22:17:59 +01:00
Andreas Shimokawa 26a751977e Pebble: try to improve PebbleKit compatibility
(Might help with glance #506)
2017-01-18 22:10:10 +01:00
Andreas Shimokawa ed020c2a97 Pebble: raise limit of appinfo.json. Some pbws have huge ones :/
Fixes #505
2017-01-18 21:47:15 +01:00
ivanovlev 0094805359 ChangeLog 2017-01-17 23:24:03 +03:00
ivanovlev cbc91e7fef Moving transliteration call from module "HPlus" to common support 2017-01-17 23:07:12 +03:00
Carsten Pfeiffer e226a97c73 Merge pull request #503 from jpbarraca/hplus-fix-1
HPlus: fixed bug setting current date
2017-01-17 00:04:39 +01:00
João Paulo Barraca 5222cf99a2 HPlus: fixed bug setting current date 2017-01-16 22:04:52 +00:00
Andreas Shimokawa f19541c654 update translations from transifex (minus French patient) 2017-01-15 21:43:06 +01:00
ivanovlev bfe24dd9f0 Refactoring 2017-01-15 22:46:30 +03:00
ivanovlev 2de9580dea Added diacritic convertation into Transliteration 2017-01-15 22:10:12 +03:00
Daniele Gobbetti 26a349210e Pebble: make the text in the dummy weather configuration activity visible. 2017-01-15 18:19:30 +01:00
Andreas Shimokawa d9d153c463 move WeatherNotificationConfig.java to its previous location to fix a crash 2017-01-15 12:43:26 +01:00
ivanovlev d08972e82a StyleCop 2017-01-15 13:41:38 +03:00
ivanovlev 01d9a63e8b Merge branch 'master' into master 2017-01-15 12:27:25 +03:00
ivanovlev 074394cba4 Transliteration is moved to a separate class, added settings option 2017-01-15 12:24:36 +03:00
Andreas Shimokawa b0991d3869 fix typo 2017-01-15 00:33:54 +01:00
Andreas Shimokawa ce67bf2c52 Pebble: make the feature to automatically delete notifications from the pebble optional
(This is not pebble specific at all but as long as other devices do not use that it will stay in the Pebble specific preference screen)
2017-01-15 00:10:40 +01:00
ivanovlev b9249065eb Fix for send message from debug screen 2017-01-14 23:01:44 +03:00
Daniele Gobbetti 4dfef382a9 Pebble: change the overflow menu of the weather system app.
If the weather notification app is not installed, link to fdroid (app if installed, web page of the app if not).
If the weather notification app is installed, show the options to activate and deactivate it.
2017-01-14 18:19:41 +01:00
Daniele Gobbetti 0152e7ce02 Make the text in the Weather configuration mock activity a bit more clear. 2017-01-14 15:10:57 +01:00
Daniele Gobbetti cb3460912f Update changelog 2017-01-14 14:53:56 +01:00
Andreas Shimokawa 23a1663384 fix wording (style -> skin) 2017-01-14 11:16:01 +01:00
ivanovlev c873312831 Transliterate unsupported Russian characters into English 2017-01-14 02:39:36 +03:00
cpfeiffer 1e24fa7ad8 Dummy weather notifucation config activity 2017-01-14 00:26:47 +01:00
Andreas Shimokawa 38e234552d Pebble: only ACK appmessages from pebble to pebblekit android apps after the app actually sent one 2017-01-13 08:16:33 +01:00
Andreas Shimokawa 0218cee0e1 Pebble: fix long standing bug in uuid encoding for ACK messages (did not seem to do any harm) 2017-01-11 23:42:40 +01:00
Andreas Shimokawa 50cb3c9db3 Pebble: remove null termination from cstrings when converting to json for PebbleKit 2017-01-11 22:39:08 +01:00
Andreas Shimokawa 5e74338efe Add HPLus stuff to changelog 2017-01-11 21:43:42 +01:00
Andreas Shimokawa 453eeb0bae Update README.md
Add João Paulo Barraca to Authors
2017-01-11 08:38:44 +01:00
Andreas Shimokawa bcc4fa8e9c update CHANGELOG 2017-01-10 23:28:55 +01:00
Andreas Shimokawa 9132736428 Pebble: report current firmware string to PebbleKit (eg. "v3.12.2") not "Gadgetbridge" 2017-01-10 22:43:10 +01:00
Andreas Shimokawa 185605211d Pebble: fix bug in PebbleKit implementation regarding binary data transferred from a watchapp to a 3rd party Android app
(Fixes a bug with TCW)
2017-01-10 22:30:55 +01:00
Carsten Pfeiffer d646b6773e Merge pull request #491 from jpbarraca/hplus-handle-data
HPlus: Improves device support
2017-01-10 20:40:07 +01:00
Andreas Shimokawa f2e6ce6380 Pebble: fix incoming calls (recently broken) 2017-01-10 18:23:35 +01:00
João Paulo Barraca b92b1c08bf HPlus: Fix deprecation warning 2017-01-10 13:44:32 +00:00
João Paulo Barraca 13ec497127 Merge branch 'master' of https://github.com/Freeyourgadget/Gadgetbridge into hplus-handle-data 2017-01-10 13:23:23 +00:00
João Paulo Barraca 4cf872664c HPlus: Improved support for storing and displaying data. 2017-01-10 13:08:45 +00:00
Andreas Shimokawa 8f988de49d update changelogs 2017-01-09 18:39:37 +01:00
Andreas Shimokawa fda317671a Ignore summary information for k9 mail
(#373)
2017-01-09 18:34:22 +01:00
Andreas Shimokawa 8c0f5599a1 Do not try to remove notifications from the device in some obvious cases 2017-01-09 16:49:11 +01:00
Andreas Shimokawa 3644d5e7a6 Pebble: remove notifications when dismissed on the phone (#326)
Most of the code is generic, so it could be implemented by other devices.
I dont know what happens if multiple messages arrive in the same notification.
So, this is experimental.
2017-01-09 16:33:00 +01:00
Andreas Shimokawa c6999713d2 Pebble: use Notifications system app as parent UUID for notifications 2017-01-09 15:11:50 +01:00
Andreas Shimokawa f05b51fd83 Pebble: Add option to disable call display
Closes #494
2017-01-09 14:41:02 +01:00
Andreas Shimokawa a2c052090b update Japanese and Spanish from transifex (ignore French, sorry guys, but I dont know what is going on with CA/FR fights) 2017-01-09 14:22:54 +01:00
Andreas Shimokawa f56a4d878e fix xml for real (maybe) 2017-01-09 14:19:45 +01:00
Andreas Shimokawa 6619a16b63 fix xml 2017-01-09 14:18:58 +01:00
Andreas Shimokawa 98dc1e127e update changelogs 2017-01-09 14:11:13 +01:00
cpfeiffer 7b1ea68b62 Remove obsolete layout params
Closes #495 (thanks!)
2017-01-08 21:45:27 +01:00
cpfeiffer 96203f574f Tiny test for GattCharacteristic class 2017-01-08 21:37:43 +01:00
cpfeiffer 6c269aa089 Generic characteristic names from field names 2017-01-08 21:37:43 +01:00
Andreas Shimokawa 855a141ec4 Pebble: in AppMessageHandler provide a default implementation of handleMessage which just ACKs 2017-01-08 17:17:29 +01:00
Daniele Gobbetti 1fda1ba1b2 Pebble: try to get rid of the sleep and rely on countdownlatch instead.
Could help with #494
2017-01-08 16:48:50 +01:00
Daniele Gobbetti c1abaaa4e0 Add support for hiding the icon in the status bar and the notification on the lockscreen.
This adds proper settings to toggle GB behavior and closes #460.
2017-01-08 15:51:56 +01:00
Yar 5c8f02d054 Set notification to minimal priority
Pebble app is not showing in status bar and on lockscreen. This change is for GadgetBridge to have the same behavior.
2017-01-08 15:31:55 +01:00
Daniele Gobbetti 09cc0134db Pebble: add support for weather in some watchfaces by gh/zalewszczak
See https://github.com/zalewszczak/pebble for a list of watchfaces
#482
2017-01-08 15:27:01 +01:00
Daniele Gobbetti 7f50e0d2b7 Pebble: add support for weather in square watchface
So far celsius are forced for temperature #482
2017-01-07 22:41:10 +01:00
Andreas Shimokawa 380e3b3640 Merge pull request #489 from hackoder/master
Show K9 message body in notifications
2017-01-05 17:14:09 +01:00
João Paulo Barraca 970c6960ea HPlus: delay day slot fetch 2017-01-05 14:24:39 +00:00
João Paulo Barraca ade7161c4d HPlus: Buffer Day Slot data before commit to DB 2017-01-05 14:03:49 +00:00
João Paulo Barraca d491921b1c HPlus: Rename HPlusHandlerThread method 2017-01-05 10:36:22 +00:00
João Paulo Barraca 93ae57bd60 Refactoring class HPlusDataRecordDaySummary 2017-01-05 10:33:37 +00:00
João Paulo Barraca 051d1e7390 HPlus: fix typo 2017-01-05 00:17:28 +00:00
João Paulo Barraca ae0718c398 HPlus: Refactoring and added comments to the message decoders members 2017-01-05 00:03:54 +00:00
João Paulo Barraca 510427e30b HPlus: Refactoring. Calendar -> GregorianCalendar 2017-01-04 23:41:35 +00:00
João Paulo Barraca 91b346b23d HPlus: Enabled decoding of additional fields in day summary message 2017-01-04 23:24:17 +00:00
João Paulo Barraca 9d67394720 HPlus: Code cleanup 2017-01-04 01:46:24 +00:00
Andreas Shimokawa fd51f32765 Add Riot as a recognized chat application (including pebble color and icon) 2017-01-03 20:36:39 +01:00
Daniele Gobbetti e70d6a1260 First take at issue template 2017-01-03 12:46:22 +01:00
Hasan Ammar 0ba377bb42 Show K9 message body in notifications 2017-01-02 19:03:02 -05:00
João Paulo Barraca 547736f8f7 HPlus: removed test values 2017-01-02 10:13:34 +00:00
João Paulo Barraca 1fb4ee8a8f HPlus: Basic support for data synchronization 2017-01-02 00:58:37 +00:00
Andreas Shimokawa 507e58922f Pebble: fix decoding of byte and short appmessage data (when a native handler is present) 2017-01-01 17:58:34 +01:00
Andreas Shimokawa f25605f5a1 Pebble: First shot at TrekVolle support
Also some cleanups
2017-01-01 16:24:46 +01:00
Andreas Shimokawa 8b55110679 Pebble: allow weather to be send to watchfaces on fw < 4.x 2017-01-01 14:19:35 +01:00
Andreas Shimokawa 1722a6dc47 Pebble: minor code cleanup 2017-01-01 13:55:07 +01:00
Andreas Shimokawa 7930b7da75 Pebble: Support Healthify Weather 2016-12-31 20:08:53 +01:00
Andreas Shimokawa 5a83cb1c48 Pebble: fix npe in mario time handler
I shout stop for 2016! :D
2016-12-31 19:29:41 +01:00
Andreas Shimokawa b811247704 Pebble: Adapt MarioTime logic 2016-12-31 19:27:21 +01:00
Andreas Shimokawa 984e639e97 Pebble: push current weather to TimeStylePebble again when watchface gets started 2016-12-31 19:17:08 +01:00
Andreas Shimokawa 4e543d4b34 Pebble: rename pushMessage() to onAppStart() 2016-12-31 19:04:05 +01:00
Andreas Shimokawa 82c0f35c58 Pebble: add encodeUpadteWeather() to AppMessageHandler for easier watchface support
Now in Timestyle weather is in sync with what we get from weather notification
2016-12-31 18:56:24 +01:00
Andreas Shimokawa 19c5cbfbb9 fix tests 2016-12-31 18:13:04 +01:00
Andreas Shimokawa 266c6b8817 Pebble: send weather to Pebble when we get notified by weather notification 2016-12-31 15:56:05 +01:00
Andreas Shimokawa f12e786837 Pebble: fix mario time appmessage handler (weather condition icon hardcoded to sunny for now) 2016-12-31 13:33:50 +01:00
Andreas Shimokawa 95e6d2c740 Pebble: delete WeatherNeat handler, it was only used for testing and the watchface seems outdated 2016-12-31 12:35:40 +01:00
Andreas Shimokawa 4631e5bbaf Pebble: restore find lost device feature abused for testing 2016-12-31 12:15:44 +01:00
Daniele Gobbetti 3280607cc9 Pebble: hide the weather app menu entries on apps that aren't the weather app. 2016-12-31 10:15:08 +01:00
Daniele Gobbetti e477d22c88 Pebble: support the system weather app.
- enable/disable weather app from the watchapp list
- convert weather data to a format that can be displayed by the system app

TODO: send the weather data periodically
2016-12-30 20:14:13 +01:00
Daniele Gobbetti 0e9ce5d186 Pebble: get min/max temperature also for the forecast 2016-12-30 19:58:56 +01:00
Andreas Shimokawa 240c81ecb4 Pebble: implement weather related protocol encoding
this is not yet connected to anything...
2016-12-30 15:26:44 +01:00
Andreas Shimokawa b045d5ac26 Merge branch 'master' into feature-weather 2016-12-30 00:10:54 +01:00
cpfeiffer b2d9c357e7 Update changelog for Subsonic #474 2016-12-29 23:46:18 +01:00
cpfeiffer cde3b36968 Updated translations from transifex (thanks!) 2016-12-29 23:30:18 +01:00
cpfeiffer bf777800d2 Update changelog for #478 2016-12-29 23:27:49 +01:00
cpfeiffer 5d3c45d2c0 Mi2: Remember and use last synced timestamp in preferences
(instead of using the last sample's timestamp in the database.
The database also contains manual hr measurements and live activity
samples, so we would miss activity data before the last manual
measurement.

Closes #478
2016-12-29 23:23:20 +01:00
cpfeiffer e77c4e7bdb Mention ZeBand as supported device 2016-12-29 21:08:52 +01:00
Daniele Gobbetti b1914a140c Update contributors list and script (so that it only counts contributions to master branch) 2016-12-29 17:14:14 +01:00
Daniele Gobbetti 5f48b89dc5 Update changelog 2016-12-29 17:12:44 +01:00
cpfeiffer df1fe7c5b8 Port to UriHelper 2016-12-29 01:46:00 +01:00
cpfeiffer aadde7d1ca Port to UriHelper, fix potential resource leak 2016-12-29 01:41:47 +01:00
cpfeiffer a96a747119 Pebble: fix resource leak on app installation 2016-12-29 01:29:28 +01:00
cpfeiffer 0646eda646 Fix file potential handle leaks 2016-12-29 01:07:26 +01:00
cpfeiffer 9cea2fc3bd Update changelog for 0.16 2016-12-28 22:54:07 +01:00
João Paulo Barraca a135f51d31 HPlus: Improve initial configuration process and refactor constants 2016-12-28 13:53:17 +00:00
João Paulo Barraca fed5638782 HPlus: Refactor Sex into Gender and convert value appropriatelly 2016-12-28 13:50:56 +00:00
Andreas Shimokawa bcb522d2d0 Merge branch 'master' into feature-weather 2016-12-27 13:51:39 +01:00
Daniele Gobbetti 4ce890b5ce Pebble: support opening files from the system share intent 2016-12-27 13:02:02 +01:00
Daniele Gobbetti 353bd4651b Show Gadgetbridge in the "share" system dialog for installing firmwares, apps etc. 2016-12-27 12:20:59 +01:00
Andreas Shimokawa 440a5e071f Try to support "Subsonic" android app (#474) 2016-12-26 23:53:24 +01:00
Andreas Shimokawa 16d9279728 Merge branch 'master' into feature-weather 2016-12-26 23:03:11 +01:00
cpfeiffer bb8aff8c99 Fix axis label color live activity (dark theme) 2016-12-26 12:51:20 +01:00
cpfeiffer da494cde7b Handle live activity for Mi2 and Mi1 in the same way #448
Realtime samples now use ActivitySample.NOT_MEASURED for unknown or
invalid values.
2016-12-26 12:51:12 +01:00
cpfeiffer 8719cadc43 Mi Band: fix live activity messing up stepcount #448
live samples now report relative steps, not absolute to the current
day's stepcount.

Also live samples' steps should NOT be added to the database since
they are already counted in the regular stepcount.
2016-12-26 01:38:20 +01:00
cpfeiffer 305bd7600c Fix current realtime steps calculations #450
timestamp is already in seconds instead of milliseconds

Also: port to REALTIME_SAMPLES
2016-12-26 00:23:02 +01:00
cpfeiffer 999d3e3252 Mi1: Attempt at throttling notifications during sync #438 2016-12-25 23:27:56 +01:00
Andreas Shimokawa e8d4575261 Merge branch 'master' into feature-weather 2016-12-25 23:20:09 +01:00
Andreas Shimokawa 4925dec9f6 bump version, update CHANGELOG, not yet ready for release 2016-12-25 23:19:37 +01:00
Andreas Shimokawa 3441192d19 Pebble 2: Fix Pebble Classic FW 3.x app variant being priorized over native Pebble 2 app variant
Fixes #475
2016-12-25 23:11:49 +01:00
Andreas Shimokawa 6c5b51cd6d Merge branch 'master' into feature-weather 2016-12-25 22:53:44 +01:00
Carsten Pfeiffer a84bc16503 Merge pull request #470 from jpbarraca/hplus
Add preliminary support for HPlus devices: Zeblaze Zeband and others
2016-12-25 21:33:52 +01:00
Andreas Shimokawa 5fb05e8546 update xml changelog for 0.15.1 also 2016-12-24 23:19:22 +01:00
Andreas Shimokawa 467e90bccb Merge pull request #473 from 6arms1leg/documentation
Added (missing) battery status display feature for Mi Band 2 to the changelog
2016-12-24 23:18:16 +01:00
Andreas Shimokawa 6af95d99be Merge branch 'master' into feature-weather 2016-12-24 23:06:21 +01:00
Andreas Shimokawa 0bdcdbae54 update CHANGELOG, bump version 2016-12-24 23:04:01 +01:00
Andreas Shimokawa b5225145d4 Fix crash with unknown notification sources on Mi Band (maybe other non-Pebble devices also affected)
Pebble low level code had an own check for notification type being null, no we set it to UNKNOWN early
This regression was introduced in 0.15.0 though "Revamp Notification types Pebble (#453)"

Fixes #468
2016-12-24 22:00:24 +01:00
6arms1leg f027dc2005 Added (missing) battery status display feature for Mi Band 2 to the changelog. 2016-12-24 14:40:03 +01:00
João Paulo Barraca 649e20ad04 HPlus: Ignore duplicated messages from band 2016-12-24 00:05:51 +00:00
João Paulo Barraca 88f2d2ee4f HPlus: Fixed notification title size 2016-12-23 12:20:06 +00:00
João Paulo Barraca cd915598b0 HPlus: Improved handling of incomming calls 2016-12-23 11:46:20 +00:00
João Paulo Barraca 9dd5967f4e HPlus: Set date and time on connect 2016-12-23 10:14:03 +00:00
João Paulo Barraca 9a338c9bae HPlus: Fix text notification length 2016-12-23 01:35:18 +00:00
João Paulo Barraca 2b78f2708f Cleanup according to PR Review 2016-12-23 01:21:05 +00:00
João Paulo Barraca b7cd908fbe Improved device filter by considering the existence of a service UUID 2016-12-23 00:10:38 +00:00
João Paulo Barraca 6c186329df Cleanup HPlusSampleProvider 2016-12-23 00:08:14 +00:00
João Paulo Barraca ae9ebc1be8 Refactoring some parts. Added support for param synchronisation with band 2016-12-21 23:57:57 +00:00
Andreas Shimokawa 8c80146e16 translate some German strings 2016-12-21 22:41:26 +01:00
Andreas Shimokawa 119028827d update Japanese from transifex (thanks) 2016-12-21 21:51:20 +01:00
João Paulo Barraca 5b3ef8999f Add preliminary support for HPlus devices, such as the Zeblaze Zeband (and many others)
Working: Text and call notifications, setting most user data, date and time, heart rate monitoring, sleep monitoring (alfa)
2016-12-21 12:51:25 +00:00
Andreas Shimokawa 2b777ecba9 update Spanish from transifex (THANKS) 2016-12-20 22:58:16 +01:00
Andreas Shimokawa aa62fa8f8e Merge branch 'master' into feature-weather 2016-12-19 23:41:20 +01:00
Andreas Shimokawa 7cb2425ffd bump version, update changelogs 2016-12-19 23:40:12 +01:00
Andreas Shimokawa 2148b431ea Merge branch 'master' into feature-weather 2016-12-19 23:32:53 +01:00
Andreas Shimokawa bd5dc6bfbc Pebble 2/LE: Add setting to limit the MTU (for debugging broken BLE stacks) 2016-12-19 23:28:06 +01:00
Daniele Gobbetti 771ca948a4 Add changelog regarding #456 2016-12-19 18:20:32 +01:00
Daniele Gobbetti 846c74aa86 Forward also group summary notifications, if they contain wearable actions. Hopefully helps with #456
Tested with conversations and there is no regression. Also a specific workaround for telegram was removed, hopefully this does not break #395
2016-12-18 18:00:16 +01:00
Daniele Gobbetti f1965c7b00 Log the flags also when passing the notification to device #456 2016-12-18 11:03:54 +01:00
Daniele Gobbetti 861c655b5d Log the incoming notification flags.
Should help further diagnosing #456
2016-12-18 10:40:30 +01:00
Daniele Gobbetti 1f1a34cf25 Replace the contributors.md file with a self-generating contributors.rst file.
The contributors.rst file is a bash quine originally found here https://gist.github.com/danielegobbetti/c691740ec6f815c75c065049fdc35243
2016-12-17 18:16:28 +01:00
Andreas Shimokawa 8990e7e3da Merge branch 'master' into feature-weather 2016-12-17 00:20:16 +01:00
Andreas Shimokawa 97aed43518 Pebble: fix for previous commit
(potential crashes when installing and receiving notifications)
2016-12-17 00:17:05 +01:00
Andreas Shimokawa 9588535004 Merge branch 'master' into feature-weather 2016-12-17 00:01:37 +01:00
Andreas Shimokawa 321298e08a update CONTRIBUTORS.md
(manually remove duplicates)
2016-12-17 00:00:31 +01:00
Andreas Shimokawa 2b3592f354 Pebble: allow sending data to the pebble during installation on FW >= 3.0
It seems to be no problem to recieve notifications etc during app installation with non legacy firmwares :)
2016-12-16 23:47:59 +01:00
cpfeiffer 5f4254e39d Merge branch 'master' into new_GUI 2016-12-15 21:06:40 +01:00
cpfeiffer 375aa491d4 Another build fix 2016-12-15 21:06:17 +01:00
cpfeiffer 321c288e27 Fix compilation 2016-12-15 20:59:55 +01:00
cpfeiffer d12103e95d Merge branch 'master' into new_GUI 2016-12-15 20:31:04 +01:00
cpfeiffer caaa38ed04 Mi2: support for current battery status #449
so far we understand
- last charge date
- current level
- state normal
- state charging

and we are notified on changes.
2016-12-14 00:50:43 +01:00
cpfeiffer dd48869fa5 Mi2: move Miband2Support into the package it belongs 2016-12-14 00:28:35 +01:00
cpfeiffer ed36410500 Empty commit to get f-droid to try another build
(apparently there was a build problem on f-droid's side)
2016-12-13 17:43:59 +01:00
Andreas Shimokawa f74bb4e3f3 Merge branch 'master' into feature-weather 2016-12-12 09:05:19 +01:00
cpfeiffer efe3f2773b Mi2: mention firmware update 2016-12-11 23:43:13 +01:00
cpfeiffer 6e633e948a Updated translations from transifex (Thanks!) 2016-12-11 23:36:41 +01:00
cpfeiffer c69889d177 Simplified + fixed ArrayUtils.equals() + added lots of testcases 2016-12-11 23:30:20 +01:00
cpfeiffer eb8129f62e Remove outdated comment 2016-12-11 23:30:20 +01:00
cpfeiffer bcfc8bc110 Mi1/Mi2: fix firmware file probing for the different devices 2016-12-11 23:30:20 +01:00
Daniele Gobbetti bb5791485c Add log statements when notifications are not sent to gadget
This should help debuggin #456
2016-12-11 22:48:12 +01:00
cpfeiffer 40354f8f5a Some @Override 2016-12-11 21:31:27 +01:00
cpfeiffer 94c0d6af9d Mi2: Fix off-by-one in activity fetching 2016-12-11 21:31:27 +01:00
Andreas Shimokawa 825f2bf2e8 Merge branch 'master' into feature-weather 2016-12-11 20:27:28 +01:00
Andreas Shimokawa 31122f0b09 Pebble: Propagate watch apps launches outside of PebbleProtocol
This does not do anything yet.
2016-12-11 20:25:46 +01:00
cpfeiffer daf6d12c62 Updated changelog for Mi2 fw update 2016-12-11 02:26:45 +01:00
cpfeiffer 6dfc895303 Mi2: Initial work on firmware update #427 2016-12-11 02:11:58 +01:00
cpfeiffer 4a19046301 Mi2: Some characteristics and opcodes for fw update 2016-12-11 02:11:58 +01:00
cpfeiffer fd2c182714 Mi 2: another characteristic rename (0000005) 2016-12-11 02:11:58 +01:00
cpfeiffer 83ad2a9bd9 Mi 2: rename characteristic 00000003 to "CONFIGURATION" 2016-12-11 02:11:58 +01:00
Andreas Shimokawa f63a7db5f9 Pebble: map owm conditions to TimeStyle icons
This is probably not the way we should do it, just experimenting for personal use :P
2016-12-11 00:08:57 +01:00
Daniele Gobbetti a6a2c6d6d6 Pebble: timestyle doesn't use Yahoo anymore.
The values do not work anyway, because it's mapping them internally. See https://github.com/freakified/TimeStylePebble/tree/master/src/pkjs for the mappings.
2016-12-10 15:09:22 +01:00
Andreas Shimokawa 779699cd95 Pebble: remove configuration stuff from TimeStylePebble handler, update weather keys
This is not usable now, just playing around
2016-12-09 23:31:32 +01:00
Andreas Shimokawa a0e21d7c6d Merge branch 'master' into feature-weather 2016-12-09 23:29:54 +01:00
Andreas Shimokawa 0e1287e382 Pebble: also execute AppMessageHandler.pushMessage() if present when connection gets established
This does not have any effect now since pushMessage() is not used in this branch
2016-12-09 23:21:51 +01:00
Andreas Shimokawa efb1cd389b Merge branch 'master' into feature-weather 2016-12-09 21:52:55 +01:00
Daniele Gobbetti 388c47ea29 Add some changelog items 2016-12-09 20:25:00 +01:00
Andreas Shimokawa 6d713a9be5 update Japanese and Spanish from transifex (thanks) 2016-12-09 20:15:28 +01:00
Andreas Shimokawa 17b581022b optimize imports 2016-12-09 20:14:17 +01:00
Kevin Richter 34296c021f Revamp Notification types Pebble (#453)
* Remove notification switches for enum & hashmap
* Fix code style
* Fix null reference exception
* Add whatsapp support
* Remove duplicate entry
2016-12-09 17:54:19 +01:00
Daniele Gobbetti 3e9898d86c Pebble: call the "ready" method also when returning from the external web browser
Additionally don't call decodeURIcomponent as the getURLVariable function already does this. Needed by apps like "slides of time". Closes 454
2016-12-09 11:55:11 +01:00
Andreas Shimokawa 313499abd4 Pebble: also display debug level in app logs output 2016-12-08 23:20:00 +01:00
Andreas Shimokawa f735739396 bump version, update changelog, add pebble time 2 to changelog as it should work 2016-12-08 19:27:25 +01:00
Daniele Gobbetti 2d0489960e Pebble: boilerplate code to open dictation session #189
This is just boilerplate code, doesn't do anything but will do no harm as it's not called from anywhere
2016-12-08 16:38:31 +01:00
Daniele Gobbetti 13b761c073 Liveview: add notice to changelog 2016-12-08 09:17:45 +01:00
Andreas Shimokawa ed38e524bf Make some static stuff non-static (potentially fixes instant run problem and leaks) 2016-12-07 23:13:51 +01:00
Daniele Gobbetti 259eb51784 Merge pull request #445 from Freeyourgadget/liveview
Liveview: Initial support for Liveview devices
2016-12-07 13:21:40 +01:00
Daniele Gobbetti 6a09023c24 Liveview: add some sanity checks to the incoming data lenghts 2016-12-07 13:09:15 +01:00
cpfeiffer 04c3bc8203 Updated README wrt bonding Mi Band a little bit 2016-12-06 21:28:16 +01:00
Carsten Pfeiffer 82a05e29df Merge pull request #446 from carlos-ferreira/patch-1
Updated known issues for Mi Band
2016-12-06 21:23:23 +01:00
Andreas Shimokawa 9e8aae3b2c update Japanese from transifex (thanks!) 2016-12-05 22:51:48 +01:00
Daniele Gobbetti 4eb56eb9ca Liveview: refactor the BT stream reading functionality
Read the expected number of bytes at each step instead of a single byte.
2016-12-05 17:45:03 +01:00
Carlos Ferreira 4f08e18073 Updated known issues for Mi Band
I was trying to "bond" the band with Gadgetbridge , but it only worked after unpairing from MiFit app and later trying again on Gadgetbridge.
2016-12-05 16:23:08 +00:00
Daniele Gobbetti e53b8b6b32 Liveview: ignore the SocketTimeoutException, disconnect in any other case 2016-12-04 21:04:30 +01:00
Daniele Gobbetti e773b71194 Liveview: add device and supported features in README 2016-12-04 20:46:34 +01:00
Daniele Gobbetti 219cc7bff1 Merge branch 'master' into liveview 2016-12-04 20:42:29 +01:00
cpfeiffer 928bdd5d36 Adjust comments to current values
They look plausible compared to 1S activity
2016-12-04 20:25:20 +01:00
Daniele Gobbetti 8c01123a48 Liveview: addressed the first feedback.
- centralized string encoding and byte order
- replaced printStrackTrace with LOG.error
2016-12-04 19:10:58 +01:00
Andreas Shimokawa 17e9c7e331 fix strings accidentially swapped 2016-12-04 17:26:55 +01:00
Andreas Shimokawa 013029443b Pebble: Minor cleanups 2016-12-04 17:21:29 +01:00
Andreas Shimokawa a691cd0ff7 Pebble: add option to enable applogs (debug messags from pebble apps) 2016-12-04 16:55:27 +01:00
cpfeiffer c1c6e37066 Mi2: update features in README 2016-12-03 23:05:06 +01:00
Daniele Gobbetti e0a844b60a Liveview: Initial support for Liveview devices
Working so far: stable connection, setting time and sending notifications.
2016-12-03 16:20:02 +01:00
Andreas Shimokawa 4763731c4e update changelog, bump version 2016-12-02 23:48:58 +01:00
Andreas Shimokawa 3db009e77d Pebble: do not dump all LE traffic, minor code cleanups 2016-12-02 23:38:54 +01:00
Andreas Shimokawa ae2c107ed1 Pebble 2/LE: only notify once when first PP packets are incoming 2016-12-02 08:50:02 +01:00
Andreas Shimokawa bc9041a4c9 Pebble 2/LE: on connect wait 30s max and stop waiting immediately if a real PP connection got establish (ie write request on the gatt server) 2016-12-02 08:07:44 +01:00
Andreas Shimokawa 3eda2d4b81 Pebble 2/LE: honor reconnect tries 2016-12-02 00:38:53 +01:00
cpfeiffer 44f74270df Mi2: Experimental support for activity recognition 2016-12-02 00:22:06 +01:00
cpfeiffer da297ecd8b Fix + cleanup time setting and calendar sending #441 2016-12-01 22:51:08 +01:00
Carsten Pfeiffer dbe90d7ae3 Merge pull request #443 from uwehermann/fix_typos
Fix typos
2016-12-01 21:11:36 +01:00
Uwe Hermann 0746aaa579 app: Random typo and consistency fixes. 2016-12-01 20:22:12 +01:00
Uwe Hermann 6dd74d04ac code/docs: Consistently use the "Gadgetbridge" spelling.
"Gadgetbridge" appears to be the common spelling as per most docs,
code comments, wiki, and so on. Thus avoid using "GadgetBridge".
2016-12-01 17:28:51 +01:00
Uwe Hermann 1352575f12 changelog_master.xml: Random typo and consistency fixes. 2016-12-01 17:22:29 +01:00
Uwe Hermann 82bc89f042 CHANGELOG.md: Random typo and consistency fixes. 2016-12-01 17:20:10 +01:00
Uwe Hermann 2846b9cc15 README.md: Random typo and consistency fixes. 2016-12-01 17:10:14 +01:00
Andreas Shimokawa f0789cc147 Pebble 2/LE: fix another bug when reconnecting 2016-12-01 00:28:23 +01:00
Andreas Shimokawa 2993bb6b5c Pebble 2/LE: fix potential NPE 2016-12-01 00:20:21 +01:00
Andreas Shimokawa 74c20f3a82 Pebble 2/LE: More fun with reconnect 2016-11-30 23:56:58 +01:00
Andreas Shimokawa b878fa5eda Pebble LE/Pebble 2: Fix reconnect not working when first attempt fails 2016-11-30 22:28:34 +01:00
Andreas Shimokawa 95ec1fb44c Pebble LE/Pebble 2: Improve reconnect 2016-11-30 19:54:21 +01:00
cpfeiffer 092012ab31 Changelog for 0.14.3 2016-11-29 23:28:15 +01:00
cpfeiffer 09ff95eb34 Support for continuous hr readings (live activity) #323 2016-11-29 23:25:11 +01:00
cpfeiffer 49acde118d Do not log heartrate separately during miband 1s sync 2016-11-29 23:25:11 +01:00
Andreas Shimokawa 1862b59dad bump version and add changelog 2016-11-29 22:36:05 +01:00
cpfeiffer 011646b097 Fix activity data on Mi Band 1 #440 2016-11-29 22:22:54 +01:00
Andreas Shimokawa 2677dad873 Fix a recent regression that caused the database migration dialog to never pop up 2016-11-27 22:50:21 +01:00
Andreas Shimokawa 109a032f1e Pebble: fix Pebble LE address not being propery added to device attributes 2016-11-27 22:45:57 +01:00
Carsten Pfeiffer d9e20b161a Merge pull request #437 from Almtesh/master
French translation.
2016-11-27 21:02:07 +01:00
Daniele Gobbetti 84327a5b86 Pebble: use also the device address as seed to store app configuration
This is not needed as long as one GB instance is used to manage a single pebble device, if multiple devices are managed the stored watchapp configuration could be messed up.
2016-11-27 11:31:03 +01:00
Andreas Shimokawa fa8df9f552 add missing migration script 2016-11-27 10:52:43 +01:00
Andreas Shimokawa 16b4bfd0e7 Pebble LE: also return to control center if successfully initialized 2016-11-27 10:38:16 +01:00
Andreas Shimokawa 723ad53588 Call getDevice always when connected
This fixes a longstanding bug where device attributes where ONLY updated when fetching activity data or when pairing a new device
2016-11-27 10:32:56 +01:00
Andreas Shimokawa 24752d3455 Pebble: try harder to get LE address into the database, does not work 2016-11-27 10:10:50 +01:00
Andreas Shimokawa 34ad088b88 Pebble: Experimental support for BLE on all models via dev option in Pebble Settings
HOWTO:
1) Pair you normal Pebble (not necessary if already done), make sure it was connected once
2) Unpair your LE pebble if already paired
3) Switch on "Always prefer BLE" in Pebble Settings
4) Tap on the + in Control Center to add a new device
5) Pair your Pebble-LE XXXX or Pebble Time LE XXXX inside Gadgetbridge's Device Discovery actibity

Now Gadgetbridge will connect to your LE Pebble when tapping on Pebble XXXX if "Always Prefer BLE" option is enabled.
You can easily switch back to classic LE by turning that option off again
2016-11-27 09:49:28 +01:00
cpfeiffer 2f7eb9ef23 Some more improvements to discovery
- pass service uuids to GBDeviceCandaidate so that DeviceCoordinators
  can detect devices by their services.

Note: they should not rely on service uuids being available
2016-11-27 02:46:07 +01:00
cpfeiffer b9ff2cd468 A few improvements to discovery
- display the right icon for found device candidates
- scan for specific LE services
2016-11-27 01:09:20 +01:00
Gilles Émilien MOREL c84003c1c0 Allumer instead of Alumer 2016-11-25 19:37:21 +01:00
Andreas Shimokawa b2e86ca061 fix xml in changelog 2016-11-25 14:55:30 +01:00
Andreas Shimokawa 352fc1a030 fix wrong return value 2016-11-25 14:53:12 +01:00
Andreas Shimokawa 6106dda2a3 bump version, update changelog 2016-11-25 14:31:56 +01:00
Andreas Shimokawa a5263141d7 Pebble 2: Ignore all GATT communication with all other that the current device
Fixes a bad bug where disconnecting from another BLE device caused the Pebble2 to disconnect
2016-11-25 14:28:04 +01:00
cpfeiffer 2d4645f6cc Fix only the first day label being displayed in Week Steps charts 2016-11-24 23:35:10 +01:00
Andreas Shimokawa 79eb4f32df update Japanese and Spanish from transifex (thanks!) 2016-11-24 22:44:18 +01:00
Andreas Shimokawa 84caf22479 fix weekly charts to start y axis at 0 2016-11-24 22:27:44 +01:00
cpfeiffer 7da328d5db Fix an invalid leftover check for array length
Also removed a method invocation that did not belong there.
2016-11-24 22:16:04 +01:00
Andreas Shimokawa df4293108a update changelog (again) 2016-11-24 22:11:59 +01:00
Andreas Shimokawa 9d083e2330 Pebble: add Kickstart Watchface to app manager on FW 4.X
I know it only exist on 4.3+, but I am lazy
2016-11-24 22:10:32 +01:00
cpfeiffer 02e6ce02b2 Add to 0.14.1 changelog 2016-11-24 22:03:02 +01:00
cpfeiffer 3fdfb7d172 Mi2: Support for setting the fitness goal (steps) 2016-11-24 21:58:32 +01:00
cpfeiffer 9bebf1d32f When memory is really low, free up some memory #436
(although we probably can't save much)
2016-11-24 21:15:27 +01:00
cpfeiffer 60cb67c3c8 Some cleanup 2016-11-24 21:15:27 +01:00
cpfeiffer cc0fbff297 Set the sample provider in getLatestActivitySample() 2016-11-24 21:15:27 +01:00
Andreas Shimokawa 6520b46238 bump version, update changelogs 2016-11-24 19:01:15 +01:00
Andreas Shimokawa 381323011e Pebble 2: work around FW installation problems
Wait a 0.5s after each complete Pebble packet and do not longer wait at all after a completed GATT package chunk (was 0.1s)
Big outgoing messages will be now much faster such as firmware installations but smaller take more time than before.

(This is not the proper fix but I could update the firmware this way, failed 10 times before after a few percent)
2016-11-24 18:52:58 +01:00
Daniele Gobbetti 5b804effa4 Pebble: use a proper event handling in the configuration JS
This allows more advanced configuration pages to work properly. The problematic config pages emerged while fixing #431
2016-11-24 18:03:47 +01:00
Gilles MOREL a5a5e66c62 French translation.
Changed and added several French messages.
2016-11-24 17:06:43 +01:00
cpfeiffer 67d89ce1b9 Create the correct start-sync timestamp #323 2016-11-22 22:52:26 +01:00
cpfeiffer dfbaba4cb6 Make sure that the charts display the requested time range
(instead of only the range with available samples)
2016-11-22 15:53:58 +01:00
cpfeiffer 8d6888a13a Remove "(next)" from 0.14.0 2016-11-22 14:43:07 +01:00
cpfeiffer a8a7d8db31 Mi2: WIP synchronize only new data since last sync
(#323)
2016-11-22 00:05:41 +01:00
cpfeiffer 0c51f86afc Mi2: more work on activity data #323 2016-11-22 00:05:41 +01:00
cpfeiffer 82cd06f4c1 Mi2: WIP: initial support for activity data
(#323)
2016-11-22 00:05:41 +01:00
cpfeiffer dbe96582a7 Changelog for 0.14.0 2016-11-22 00:04:12 +01:00
Andreas Shimokawa 4ed7906a9e update README regarding Pebble 2 2016-11-21 17:56:29 +01:00
Andreas Shimokawa 9dd61031f0 update Japanese from transifex. THANKS! 2016-11-21 17:46:11 +01:00
Andreas Shimokawa 8cb2030478 update changelogs (mi2 missing) 2016-11-21 17:44:43 +01:00
Andreas Shimokawa eb052cead3 Pebble 2: Also honor the mtu when client gets a change reported
Should help older LE Pebbles
2016-11-21 17:29:16 +01:00
Andreas Shimokawa 647b67cfca Pebble 2: call both requestMTU and use the characteristic write 2016-11-21 15:17:03 +01:00
Andreas Shimokawa fce86482b9 Pebble 2: try an alternative way of setting the MTU (might revert) 2016-11-21 14:54:01 +01:00
Andreas Shimokawa e8da301da3 Pebble 2: fix a few crashes with disconnect/ reconnect 2016-11-20 22:04:49 +01:00
Andreas Shimokawa 4f3c46f704 Pebble 2: only add second service if first one was added sucessfully 2016-11-20 00:12:41 +01:00
Andreas Shimokawa 3b250a4568 Pebble 2: initial hacky reconnect support
Works sometimes, at least less crashes
(#432)
2016-11-19 23:53:07 +01:00
Andreas Shimokawa c95587c915 remove _le suffix where not appropriate 2016-11-19 22:45:42 +01:00
Andreas Shimokawa 029cc02a29 Pebble: Add Pebble specific pairing activity mainly useful for Pebble 2 since it properly pairs the Pebble after connecting with BLE
Fixes #433
2016-11-19 22:13:00 +01:00
Andreas Shimokawa ddfab1cdae Pebble 2: use requestMTU() on Android 5+ instead of characteristics write.
This actually fixes MTU problems with older Pebbles (Time, OG)
2016-11-18 23:47:48 +01:00
Andreas Shimokawa 4dc085de57 Pebble 2: React to MTU notifications, fixes LE for older pebbles also
It seems that setting the MTU on older pebbles does not work, so just use what we can use.
Maybe old pebbles need setMTU() which only works on Android 5+, we could use that conditionally...
2016-11-18 23:47:48 +01:00
cpfeiffer 51fa31aa66 Fix BLE type conversions problems 2016-11-18 23:36:22 +01:00
Andreas Shimokawa 66e3de9168 Pebble 2: improve connection problems (at least for me) 2016-11-18 22:38:39 +01:00
cpfeiffer 96a16245df Update to MPAndroidChart 3.0.1 2016-11-18 21:31:55 +01:00
Daniele Gobbetti 42901a295d Pebble: pretend the clay-settings key is always present (but empty per default) in the localStorage 2016-11-18 12:33:12 +01:00
Andreas Shimokawa d41848014b fix database schema migration from 13 to 14 2016-11-16 11:37:36 +01:00
Andreas Shimokawa 485cda52a8 Pebble 2: actually report HRM feature to Charts Activity 2016-11-15 23:41:35 +01:00
Andreas Shimokawa d7256d172e I hate java 2016-11-15 23:23:21 +01:00
Andreas Shimokawa 163a7bdf15 Pebble 2: save heart rate values to database (hopefully) 2016-11-15 23:21:13 +01:00
Andreas Shimokawa 1012236989 bump version, not yet release time! 2016-11-15 12:29:18 +01:00
Andreas Shimokawa 4a243ff361 update Japanese and German from transifex (THANKS!) 2016-11-15 12:22:41 +01:00
Andreas Shimokawa 82a47022fa Pebble 2: allow to disable/enable HRM in context menu of Workout app
Okay this might be stupid  since Workout seems to work also works without HRM!
I assumed (wrongly) that I will disable Workout by disabling HRM

anyway, better than not being able to toggle it at all!
2016-11-15 12:17:51 +01:00
Andreas Shimokawa 4b7f47ba6c Pebble 2: add Workout system app to app manager on P2 and hexdump incoming HRM datalog 2016-11-15 11:56:14 +01:00
Daniele Gobbetti 1a22752b98 Pebble: enable localStorage
LocalStorage native functions are overridden to keep each watchface's settings separated. It's possible the bind method do not work on older versions of android.
2016-11-14 17:51:33 +01:00
cpfeiffer d8145a52f9 Activate the display when lifting the wrist #323
Configurable, defaults to true
2016-11-13 21:47:54 +01:00
Andreas Shimokawa 00a71f53b3 Pebble 2: add not yet working code (at least not without weird workarounds)
This is is a pain because of tons of weird pairing issues
2016-11-13 21:29:58 +01:00
cpfeiffer d89899557c Mi2: Add config option to configure date/time display
(added a new EventHandler method to set a specific configuration option)
2016-11-13 20:47:24 +01:00
cpfeiffer ddaf51768d Improved logging 2016-11-13 20:24:25 +01:00
cpfeiffer 3cc8d887ca Mi2: Initial support for wear location, hr during sleep and date format #323
- wear location from prefs is sent to the band
- hr measurement during sleep is sent to the band
- date format is sent to the band (date+time or just time)
  (TODO: needs preference option)
2016-11-13 01:44:52 +01:00
Carsten Pfeiffer 1751c86479 Merge pull request #430 from 6arms1leg/documentation
Updated documentation ("Supported Devices" section, Mi Band 2 info)
2016-11-11 21:34:42 +01:00
6arms1leg 37eb60b60c Updated documentation ("Supported Devices" section, Mi Band 2 info) 2016-11-11 02:39:01 +01:00
Andreas Shimokawa f68bbe453b Pebble: bump flags and version we report
Plus some small preparations for P2 support which do not hurt in master
2016-11-10 11:05:32 +01:00
Andreas Shimokawa 1fcd7d8144 bump version, update changelogs 2016-11-10 10:51:04 +01:00
Andreas Shimokawa eb7646d26a Pebble: Fix vanished Health system apps (for affected users)
This code also allows us to add new system apps which will then appended to the current list of previous Gadgetbridge users.
2016-11-09 19:20:37 +01:00
Andreas Shimokawa 837dfd5917 Pebble: fix Pebble Health vanishing when deactivating 2016-11-09 12:06:13 +01:00
cpfeiffer 3b474bb5a9 Move another notify registration to after initialization #408 2016-11-08 22:55:31 +01:00
cpfeiffer 4843f7fc2b More details regarding Mi hr features 2016-11-08 20:19:47 +01:00
cpfeiffer d0beee3df6 Updated documentation about features and how to use with Mi Bands 2016-11-08 20:17:51 +01:00
cpfeiffer 705912172d Adjust changelog to confirmed fixed bugreports 2016-11-07 22:22:59 +01:00
Daniele Gobbetti 16c4f1a5ca Pebble: add toggle to use last known location for sunrise and sunset
This adds the feature discussed in #415, the used location is the last recorded by the network location provider, if it's not available then the stored location is used.
2016-11-06 15:42:52 +01:00
cpfeiffer 119c225ec4 Updated translation from transifex (thanks!) 2016-11-04 22:29:58 +01:00
cpfeiffer 4c1b7e0328 Release 0.13.8 2016-11-04 22:19:51 +01:00
cpfeiffer 55f036c104 Move heart rate notification enamblement to after authentication
Might fix #408 and #425
2016-11-04 22:11:07 +01:00
Andreas Shimokawa 14ef5202e1 some internal testing which does not hurt in master 2016-11-03 11:07:44 +01:00
cpfeiffer 0076bbf572 Remove all Mi2 DeviceInfo stuff -- they're not used together #365
This might fix firmware downgrading on certain Mi1A devices
with hw revision 8
2016-10-30 23:04:21 +01:00
Daniele Gobbetti 879e47760b Stop discovery also if the user swipes the snackbar away. 2016-10-30 15:21:06 +01:00
Daniele Gobbetti 8c769b15c3 Use snackbar instead of progress dialog to find device. 2016-10-30 15:15:34 +01:00
Daniele Gobbetti a45f76d3bf Prevent disconnection if device is not connected. 2016-10-30 14:42:08 +01:00
Daniele Gobbetti 46824b7235 Minor changes to the navigation header 2016-10-30 14:39:31 +01:00
Daniele Gobbetti d087e2142d Merge branch 'master' into new_GUI 2016-10-29 20:54:21 +02:00
Daniele Gobbetti b9bfb8c93a Use attr resources to enable theme dependent colors for the charts. 2016-10-29 20:29:35 +02:00
Andreas Shimokawa e50574d23c change string "Open in Pebble Appstore" in "Search in Pebble Appstore" 2016-10-29 19:01:39 +02:00
Andreas Shimokawa 2b834f96c9 update changelog 2016-10-29 19:00:53 +02:00
Daniele Gobbetti 8fdb233ef0 Bind connect and disconnect to the whole cards.
Use snackbars to give early feedback to the user (colors are wrong now, waiting for a change in master).
Removed hintTextView for good
2016-10-29 18:20:53 +02:00
Daniele Gobbetti a4b7b87b24 Merge branch 'master' into new_GUI 2016-10-29 16:40:33 +02:00
Daniele Gobbetti e2a9574406 Add logic to open zip files. Lately pebble apps are recognized as such. 2016-10-29 16:27:31 +02:00
cpfeiffer a4f615ce71 Update changelog for 3.1.3 2016-10-29 16:08:20 +02:00
cpfeiffer d442030c2a Revert to robolectric 3.1.2 as 3.1.3 does not work at all. 2016-10-29 15:55:27 +02:00
Andreas Shimokawa e0d78e8208 update German and Japanese from transifex (thianks!) 2016-10-29 12:58:19 +02:00
Andreas Shimokawa 5b73690972 update changelogs again (mi band 2 stuff missing) 2016-10-29 12:00:27 +02:00
Andreas Shimokawa f755d99023 Pebble: Add context menu option in app manager to search a watchapp in the pebble appstore 2016-10-29 11:41:08 +02:00
cpfeiffer c2a9348c6e Upgrade mpandroidchart and robolectric 2016-10-29 00:51:00 +02:00
cpfeiffer 8187c6d207 Fix message placeholder 2016-10-29 00:43:59 +02:00
cpfeiffer cea5f5fa36 Remove duplicate case here as well 2016-10-29 00:01:56 +02:00
cpfeiffer 1cadb692fe Wild guess at trying to fix spontaneous reboots during discovery #412 2016-10-28 23:48:13 +02:00
cpfeiffer a941a6cd5f Make some strings translatable 2016-10-28 23:47:39 +02:00
cpfeiffer e75f4f84e1 Hint: you need to enable location for device discovery. 2016-10-28 23:27:25 +02:00
cpfeiffer 3db9748136 Mi2: Attempt to support non-repetitive alarms #323 2016-10-28 23:18:10 +02:00
cpfeiffer e5ade5c0ef Add Alamr.isRepetitive() convenience method 2016-10-28 22:40:34 +02:00
cpfeiffer 1352403089 Mi2: Allow for disabling alarms #323 2016-10-28 22:26:07 +02:00
cpfeiffer 544ec4958b Allow multiple lines for name in "item_with_details" (fw upgrade log) 2016-10-28 22:18:08 +02:00
cpfeiffer afe5f17e5a bump gradle build tools to 2.2.2 2016-10-28 21:39:40 +02:00
Carsten Pfeiffer 247023a57f Merge pull request #423 from xzovy/patch-1
Fixed typo in README.md
2016-10-28 21:18:25 +02:00
xzovy d73b3137cb Fixed typo in README.md
Fixed typo on line 40.
2016-10-28 14:56:56 -04:00
Andreas Shimokawa 300d0466af Merge pull request #422 from roidelapluie/fcb
Mark 3rd party facebook apps notifications as Facebook
2016-10-28 10:45:40 +02:00
Andreas Shimokawa 55daaf247c Merge pull request #420 from atkyritsis/master
Removed duplicate if statement.
2016-10-28 10:45:09 +02:00
Julien Pivotto 67937dd6ee Mark 3rd party facebook apps notifications as Facebook
- Toffeed: https://f-droid.org/repository/browse/?fdid=me.jakelane.wrapperforfacebook
- Slimsocial: https://f-droid.org/repository/browse/?fdid=it.rignanese.leo.slimfacebook
- MaterialFBook: https://f-droid.org/repository/browse/?fdid=me.zeeroooo.materialfb

Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
2016-10-28 07:23:54 +02:00
Andreas Shimokawa 8603c3ffa0 Merge branch 'master' into new_GUI 2016-10-28 00:53:03 +02:00
Andreas Shimokawa 9a41d4d7a2 bump version, update changelog 2016-10-28 00:51:29 +02:00
Andreas Shimokawa d6b9e6d64b Pebble: Support sending byte arrays from app configuration data
Also add debug output if trying to encode unknown classes in PebbleProtocol

(Fixes #421)
2016-10-28 00:32:45 +02:00
Andreas Shimokawa bdf403210e Pebble: Fix configuration of certain pebble apps
for appkeys with index 0 it was assumed they were not found becaus JSONObject.getOpt() returns 0 if not found.
Use the getOpt() method variant with a fallback parameter instead and set that to -1 fixes the problem.

(Also fixes a missing debug output)

Fixes #419
2016-10-28 00:03:50 +02:00
atkyritsis 45cf4e5396 Removed duplicate if statement. 2016-10-27 18:44:27 +02:00
Andreas Shimokawa 4edfc44d64 Merge branch 'master' into new_GUI 2016-10-26 00:08:05 +02:00
Andreas Shimokawa d3571d53b2 Mi Band: allow to remove mac address from preferences
(this allows to clear an extra mi band from the control center)

Evil hack...
2016-10-26 00:03:54 +02:00
Daniele Gobbetti ee1cf74a7b ControlCenterv2: hotfix for referenced textview hidden by previous commit 2016-10-25 18:03:45 +02:00
Daniele Gobbetti d467b37493 ControlCenterv2: further steps:
Close drawer before launching activities (feels sloow).
Implement device deletion (untested).
Add app-management icon, remove tap-connected-device-for-primary-activity, hidden (not removed) text hint.
Use level-list for device icon.
Use the new control center when tapping GB notifications.
Added icons to the legacy control center context menu, perhaps it can be embedded in the card?
2016-10-25 17:49:21 +02:00
Daniele Gobbetti d93a5be57a Merge branch 'master' into new_GUI 2016-10-25 17:43:00 +02:00
Daniele Gobbetti 1f77e3e84f Coordinator: add explicit methods to determine if the device supports app management and which class is responsible for it. 2016-10-25 17:39:11 +02:00
cpfeiffer 59212b54c8 CCv2: Less contrast by using grey icons.
Delete and Find Device icons are even lighter
2016-10-24 21:09:39 +02:00
cpfeiffer d302a0a5c3 Merge branch 'master' into new_GUI 2016-10-24 20:03:24 +02:00
cpfeiffer a39e3a035c Move device deletion logic to DeviceCoordinator and subclasses 2016-10-24 20:02:44 +02:00
Daniele Gobbetti cde09d71bc Fixed KitKat crashes and changed to RelativeLayout. Using CCv2 as default launcher activity.
Added lost-device icon and action, added background to buttons.
Overflow reveal is now animated inside the card.
Bind connect and disconnect actions to device-icon (short press to connect/launch default activity; long press to disconnect).
2016-10-24 17:41:56 +02:00
cpfeiffer 84f36b528a Merge remote-tracking branch 'origin/master' into new_GUI 2016-10-24 11:32:39 +02:00
cpfeiffer de46555e37 Mi2: display heart rate result from Debug screen as a toast #323 2016-10-24 00:23:59 +02:00
cpfeiffer b20a9c9ccc Some initial hacky support for hr readings (Debug activity only)
My Mi2 stopped reporting hr values a while ago though, even on-device.
2016-10-23 23:53:48 +02:00
cpfeiffer 069abe17b7 Release 0.13.6 2016-10-23 23:14:05 +02:00
cpfeiffer 17b70a1b82 Support for more than one alarm #323
Smart alarms not supported yet.
2016-10-23 23:05:54 +02:00
cpfeiffer 3a12ffd42d Mi2: Fix for alarm enablement. Was too late, apparently. 2016-10-23 22:38:57 +02:00
Daniele Gobbetti c6ce516f6a Upgrade support libraries, hopefully fix crashes on KitKat. 2016-10-23 21:00:51 +02:00
Daniele Gobbetti c20747226f Add dark theme 2016-10-21 23:01:10 +02:00
Daniele Gobbetti 00938baf7d Further refinements:
- disconnect by long-pressing device icon (temporary)
- use level-list to show battery level + charging
- remove padding around cards list
- use style colors for action icons (supports dark theme)
- add secondary text to the themes, even though the color is the same
- replace the info icon with three vertical dots
2016-10-21 17:44:36 +02:00
Andreas Shimokawa 68f83d3f33 Merge branch 'master' into new_GUI 2016-10-21 13:21:32 +02:00
Andreas Shimokawa 192b8e52ed update hungarian from transifex
(that is just transifex having reformatted the translation I tx pushed)
2016-10-21 13:14:09 +02:00
Andreas Shimokawa d08012709f bump version, add changelog 2016-10-21 13:13:08 +02:00
Daniele Gobbetti 503bcee7b4 Support record version 13 (firmware 4.2) 2016-10-21 13:07:07 +02:00
Daniele Gobbetti 371f0ecdd0 Use a new GUI for the controlcenter.
The existing logic was mostly ported to the new activity, with the exception of device deletion (and all TODOs).
2016-10-21 13:01:30 +02:00
cpfeiffer ee24443b6a Make it really clear, that only the first alarm works #323 2016-10-21 01:01:30 +02:00
cpfeiffer 15954d4561 Initial support for alarms (one, atm) #323
(repetitinos not tested yet)
2016-10-21 00:50:41 +02:00
cpfeiffer 839da4f06a Shuffle some logging things around 2016-10-21 00:50:41 +02:00
cpfeiffer c87d08bf4b Small refactoring for initial support for Mi2 Alarms #323 2016-10-21 00:50:41 +02:00
Andreas Shimokawa 04673923b6 Merge pull request #414 from geripgeri/hungarian-translation
Hungarian translation updated.
2016-10-19 20:39:37 +02:00
Andreas Shimokawa 858714d73d update Spanish, Japanese and Italian from transifex (thanks!) 2016-10-19 20:16:27 +02:00
Andreas Shimokawa cc2b22cfc7 prefer BIG_TEXT extra from notification only for email
(Some messengers accumulate messages in BIG_TEXT)
2016-10-19 18:38:26 +02:00
Gergely Peidl bca408f366 Hungarian translation updated. 2016-10-19 13:38:45 +02:00
Andreas Shimokawa 336ffd5bf7 Extract EXTRA_BIG_TEXT instead of EXTRA_TEXT if available.
This makes K9 Mail usable with only using generic notifcations on Android >=5
(It shows the message preview now)
2016-10-18 23:44:00 +02:00
cpfeiffer 9dc9ad6ce4 Updated changelog for 0.13.4 2016-10-11 23:36:05 +02:00
cpfeiffer 4122e0c20c Add button "Test New Functionality" 2016-10-11 23:35:58 +02:00
Daniele Gobbetti 21fc5c7498 Integrate Daniele's live-sensor-data support 2016-10-11 23:13:40 +02:00
cpfeiffer 713989ef38 Add event "test new function" for the debug screen 2016-10-11 23:06:59 +02:00
cpfeiffer b1dcb997bb Add categories as headers for the missing toolbar 2016-10-11 22:34:04 +02:00
cpfeiffer 344f6bcaa0 Use separate AppCompatPreferenceActivity from Android samples
(instead of mixing it with our code)
2016-10-11 22:07:56 +02:00
cpfeiffer 363b7cbf28 @Override 2016-10-11 21:47:45 +02:00
cpfeiffer 7c3dc741d2 Send vibration off-commands to Mi2 (#323)
Far from perfect, but a little better.
2016-10-11 21:38:17 +02:00
cpfeiffer a559140f67 Fix the previous fix ;-) 2016-10-11 21:35:45 +02:00
cpfeiffer 1fc44034f0 Also use getGenericType() for the "summary" types 2016-10-11 21:28:28 +02:00
cpfeiffer f877a4a485 Note about preference changes for the user 2016-10-11 21:20:53 +02:00
cpfeiffer e7c0afa603 Cleanup onNotification() 2016-10-11 21:18:43 +02:00
cpfeiffer f1243f52c1 Deactivate some unsupported things for Mi2 2016-10-11 20:23:25 +02:00
Andreas Shimokawa 24220ee5d3 omg 2016-10-11 18:12:43 +02:00
Andreas Shimokawa 0fbc4d85ef fix try buttons take two :( 2016-10-11 18:10:04 +02:00
Andreas Shimokawa c65a0a16de fix mi band try vibration buttons 2016-10-11 17:57:50 +02:00
Andreas Shimokawa 09a5c7cceb fix string reference 2016-10-11 17:47:38 +02:00
Andreas Shimokawa a094f0cc76 Mi Band: for vibration settings, group navication types (email, sms, chat, social network, navigation) 2016-10-11 15:20:55 +02:00
Andreas Shimokawa cd195a5969 map more apps to notification types (icons and colors on the pebble) 2016-10-11 11:54:52 +02:00
Andreas Shimokawa 18bcfe78b9 bump version, update xml changelog 2016-10-11 10:12:05 +02:00
cpfeiffer 1100790456 Update changelog for 0.13.4 2016-10-10 23:17:21 +02:00
cpfeiffer 92c629c351 Ranem NotificationType.UNDEFINED to UNKNOWN 2016-10-10 23:06:44 +02:00
cpfeiffer 17c152596b Thy try-buttons are just dummies, don't persist 2016-10-10 22:55:27 +02:00
cpfeiffer 62828e5158 Remove ORIGIN_ notification constants, clean up
- MI preferences now use NotificationType.getFixedName() for preference
  keys
2016-10-10 22:45:26 +02:00
cpfeiffer b2d36dfb54 Remove Pebble Message vibration configuration from Mi Band prefs 2016-10-10 21:58:41 +02:00
cpfeiffer 5e9c45e8b0 K9 Mail -> Email 2016-10-10 21:55:44 +02:00
cpfeiffer 5c8525c5d0 Don't rely on File.canWrite() for directories, closes #406 2016-10-10 21:01:41 +02:00
cpfeiffer f57fec25f8 Remove the coordinator instance #323
For now, always iterate over the available coordinators instead.
Should fix MiBandSupport occasionally being used in place of MiBand2Support.
2016-10-10 20:52:40 +02:00
cpfeiffer db034a246c Add 'Try' button to vibration profile preferences, closes #405 2016-10-09 00:10:53 +02:00
cpfeiffer 9e32e7d0d3 Remove some Mi2 testing code, only use Mild Alerts for Mi1 (fow now) 2016-10-08 22:43:41 +02:00
cpfeiffer c2ff05e849 Make timestamp to mpandroidchart float x-value explicit 2016-10-08 21:27:32 +02:00
cpfeiffer 125c0092cb Storage and improved way of realtime data (hr, steps so far) 2016-10-08 01:35:58 +02:00
cpfeiffer 5a2ddaaec0 Use x-values instead of x indices for the charts 2016-10-08 01:35:03 +02:00
cpfeiffer 558c9e4664 Fiix visal glitch in weeksteps chart (first and last bar only partly visible) 2016-10-07 21:46:00 +02:00
cpfeiffer 7479c3d420 Whitelist 4.16.4.22 #369 2016-10-05 23:17:02 +02:00
cpfeiffer 713e9426b9 Re-enable UUID_PAIR again, when keeping data on device is configured) #250
This should fix data sharing with Mi Fit
2016-10-05 23:01:35 +02:00
cpfeiffer e5d178b315 Fix crash with mpandroidchart 3.0pre (master) 2016-10-04 23:39:14 +02:00
cpfeiffer 478782998e Updated changelog 2016-10-04 23:33:49 +02:00
cpfeiffer ac9008aa02 Moved disconnect back to the bottom of the context menu 2016-10-04 23:30:49 +02:00
cpfeiffer 75bca1b924 Update to latest master in mpac and fix labels for sleep chart as well 2016-10-04 00:09:56 +02:00
cpfeiffer f35f76a42b Fix extraneous space at the bottom of the week steps chart 2016-10-04 00:04:04 +02:00
cpfeiffer eccf9164f6 No need to have a separate xLabelsFormatter 2016-10-03 23:48:32 +02:00
cpfeiffer dd217c83af Updated changelog for Mi2 icons 2016-10-03 23:36:50 +02:00
cpfeiffer dee492bc4f Fixes regarding xlabels -- must set them at the correct point in time
(otherwise we get ArrayIndexOutOfBoundsException when the chart
refreshes and the wrong xLabels, potentially with fewer entries are used)
2016-10-03 23:36:04 +02:00
Andreas Shimokawa 6a5c3fb945 update version and changelog 2016-10-03 22:51:12 +02:00
Andreas Shimokawa b8b8a05181 update Japanese from transifex, thanks! 2016-10-03 22:43:40 +02:00
cpfeiffer 827c99f620 Fix weekday charts, closes #403 2016-10-03 22:42:25 +02:00
Daniele Gobbetti bbecfbeace Check if the bluetoothReceiver is still valid before unregistering it.
Do not set the scan tipe as BT classic if the discovery action has been set by BLE scans.
2016-10-03 22:31:33 +02:00
cpfeiffer dd590528dc Add back the x-labels 2016-10-03 21:53:06 +02:00
cpfeiffer f23ed5ce69 Mi2: declare to always support hr #323 2016-10-03 21:17:58 +02:00
Andreas Shimokawa ed343778ee cleanup Debug activity a bit, add Spinner and remove a lot of buttons 2016-10-03 20:51:28 +02:00
cpfeiffer 5bdc7933b3 Somewhat hacky support for Mi2 notification icons #323 2016-10-02 23:04:59 +02:00
cpfeiffer 2a0d97b39a Improved error handling, display errors when deleting device 2016-09-30 23:07:30 +02:00
cpfeiffer 09502f96c9 AlertDialog.show() is non-modal, so put code into callback. Also added unpairing. 2016-09-30 22:58:28 +02:00
cpfeiffer 2e7fb57172 Display device name to delete, and use different API to delete device 2016-09-30 22:58:28 +02:00
cpfeiffer b890242c4f Only refresh when deletion of device was confirmed 2016-09-30 22:58:28 +02:00
Andreas Shimokawa 5e63b7ce04 Vibratissimo: do not show database migration activity 2016-09-30 21:43:44 +02:00
cpfeiffer f44974c215 Use findDevice() instead of getDevice() when you want to delete it 2016-09-30 21:27:33 +02:00
cpfeiffer 1fd6b59bf8 Fix logic error 2016-09-30 20:19:06 +02:00
cpfeiffer 27c83604d3 Update DeviceManager after deleting a device 2016-09-30 20:17:20 +02:00
Andreas Shimokawa 56d8a49d5b only disconnect device when confirming delete dialog 2016-09-30 19:20:04 +02:00
Andreas Shimokawa 456fcfdd98 update French, Italian and Spanish from transifex (THANKS!) 2016-09-30 19:17:17 +02:00
Andreas Shimokawa bce28fd8ac update changelogs 2016-09-30 19:16:02 +02:00
Andreas Shimokawa 5c0618d43d show confirm dialog before actually delete device
Also delete device last (after associated data), so that we do not leak if something fails

(#401)
2016-09-30 19:04:44 +02:00
Daniele Gobbetti 42f622af85 Support record version 12 (firmware 4.1) 2016-09-30 17:28:46 +02:00
Andreas Shimokawa 30d686fa50 support deleting devices and associated data
(#401)

TODO:
- remove device from list after deleting (needs changes to DeviceManager)
- confirmation dialog!!!
2016-09-30 13:41:06 +02:00
cpfeiffer e3f15f7bd8 Sort devices lexicographically 2016-09-29 22:56:16 +02:00
cpfeiffer fbfc9ed97f Workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317 2016-09-29 22:45:17 +02:00
cpfeiffer f58b1f33c6 Fix parameter order for mpandroidchart 3.0 2016-09-29 22:45:17 +02:00
cpfeiffer b2065fd91f Update to latest mpandroidchart commit 2016-09-29 22:45:17 +02:00
cpfeiffer 1b5bc23981 Initial port to mpandroidchart 3.0
does not quite work yet
2016-09-29 22:45:17 +02:00
cpfeiffer 0a4eefcf11 Use device information from the database for GBDevices
fixes offline charts not displaying heartrate for Mi1S
2016-09-29 22:40:16 +02:00
cpfeiffer 8f36712342 WIP: A little more Alert Notification Profile
I think we should rather focus on a code generator, though.
2016-09-29 20:32:15 +02:00
Daniele Gobbetti fabc52fdad Don't forward group summary notifications to the wearable, they are meant for the android device only
Possibly also related to #395
2016-09-27 17:33:10 +02:00
Andreas Shimokawa b5373d9593 update changelogs again 2016-09-26 22:35:59 +02:00
cpfeiffer dbdd7366ed Reenable the lollipop LE scanner, but after the plain BT scanner 2016-09-26 22:30:15 +02:00
cpfeiffer c2f8037f07 WIP: Alert Notification Profile 2016-09-26 22:21:59 +02:00
Andreas Shimokawa ea76e568cc bump version, update changelog 2016-09-26 22:11:26 +02:00
Andreas Shimokawa cb232638d4 disable new btle scanning, it does not work for me :( 2016-09-26 22:07:46 +02:00
Andreas Shimokawa 5364bf6246 Update "isch aaabe gar keine Auto"-language from transifex (thanks!) 2016-09-26 21:59:26 +02:00
Andreas Shimokawa 9cccb085c4 Pebble: allow sending acks to pebblekit android apps which always use transaction id -1 (this seems to be okay for pebblekit apps which do not want to use real transaction ids) 2016-09-26 21:51:11 +02:00
Andreas Shimokawa 55a1248e8f compile fix 2016-09-26 21:43:10 +02:00
cpfeiffer d4b134a490 WIP regarding heart rate profile 2016-09-26 11:44:57 +02:00
cpfeiffer 0341c7f61f fix e.printStackTrace() by logging instead 2016-09-26 11:44:08 +02:00
JohnnySun 3259efbd10 When discovery gadget use Lollipop+ device, use new BTLE API instead of old general BT discovery.
(cherry picked from commit 53e3fde7c06cbcb7665726eeb80544730196f928)
2016-09-26 11:43:28 +02:00
Andreas Shimokawa fd03dac5cd Pebble: try to work around duplicate Telegram messages
(#395)
2016-09-22 09:31:14 +02:00
Andreas Shimokawa 8080734470 Pebble: use telegram icon for telegram messages
also use the facebook icon for the official facebook app
(untested because we would never use facebook)
2016-09-21 21:31:23 +02:00
Andreas Shimokawa 28a1768f32 update translations from transifex (thanks!) 2016-09-21 19:28:29 +02:00
Andreas Shimokawa 09c68cebad update README.md 2016-09-21 19:24:57 +02:00
Andreas Shimokawa 5bba58cf21 bump version, add changelog 2016-09-21 19:22:26 +02:00
Andreas Shimokawa c8fb7c5d10 Add icon for Vibratissimo 2016-09-21 19:16:07 +02:00
Andreas Shimokawa e1992f43e5 run optipng on all pngs 2016-09-21 09:25:33 +02:00
Andreas Shimokawa 88b3a69747 update gradle plugin to 2.2.0 2016-09-21 09:25:16 +02:00
cpfeiffer 1bd919ccaa For a start, we can reuse the alert notification from Mi 1 #323
So vibration works :-)
2016-09-20 23:41:50 +02:00
cpfeiffer ccdb843b6e Improved Mi Band 2 support #323
- connecting works and is stable
- firmware and hardware version is displayed
- time is set
2016-09-20 23:12:17 +02:00
Andreas Shimokawa 696611d392 Do not show "your activity" if activity tracking is not supported 2016-09-20 22:02:40 +02:00
Andreas Shimokawa 1f8cfa5a68 Vibratissimo: clear queue when setting vibration 2016-09-20 21:49:27 +02:00
Andreas Shimokawa 6a18d90fee Vibratissimo: add simple and buggy activity with a slider for vibration control
Also includes some fixes and "find your device" support ;)
2016-09-20 20:28:52 +02:00
Andreas Shimokawa da01a76594 fix BatteryInfoProfile NPE, show battery level for Vibratissimo 2016-09-19 16:37:45 +02:00
Andreas Shimokawa b2669d6fd7 Match manufacturer string with what we get from BLE device info 2016-09-19 15:00:24 +02:00
Andreas Shimokawa 8ba7bc7353 Add (useless) support for Vibratissimo "massage devices"
Don't take this serious. It will make the "massage device" vibrate when a phone call arrives.
It is inspired by the famous lawsuit[1] which has nothing to do with the Vibratissimo device maker.
After reading this I picked up the cheapest ble massage device just to see if we could support it.
And yes, we can.

[1] http://arstechnica.com/wp-content/uploads/2016/09/vibratorsuit.pdf
2016-09-19 12:37:41 +02:00
Carsten Pfeiffer 2ee253965b Merge pull request #391 from JohnnySun/master
Rollback gradle.properties
2016-09-16 23:18:34 +02:00
JohnnySun 23428c3a16 Rollback gradle.properties 2016-09-14 07:28:06 +08:00
JohnnySun 53d4681763 Optimize Imports 2016-09-13 23:53:35 +08:00
JohnnySun 19fbe5719c When miband2 auth success, let application show miband status is connected 2016-09-13 23:50:35 +08:00
JohnnySun 08f2b0eb7c fix somebug and rewirte the auth process 2016-09-13 18:15:03 +08:00
Carsten Pfeiffer 539bc73806 Merge pull request #386 from JohnnySun/master
Added miband2 connection support
2016-09-12 23:54:06 +02:00
JohnnySun 90d730bdc8 Added miband2 connection support 2016-09-13 01:28:50 +08:00
cpfeiffer 15e3d6565b Updated translations from transifex (thanks!) 2016-09-11 23:20:09 +02:00
Andreas Shimokawa abd298d8aa update xml changelog 2016-09-11 22:55:40 +02:00
cpfeiffer e555066ffc Fix firmware2 not being set 2016-09-11 21:15:36 +02:00
Andreas Shimokawa 40e079ad5d update android gradle plugin to 2.2-rc2 2016-09-11 14:19:45 +02:00
Andreas Shimokawa 3dea675987 bump version 2016-09-11 12:47:02 +02:00
Andreas Shimokawa 56c7b6b1cb fix sleep goal and step goal not being set in database (should fix user attribute table spamming) 2016-09-11 12:35:26 +02:00
Andreas Shimokawa 0cc95bd297 more debug 2016-09-11 12:23:36 +02:00
Andreas Shimokawa d0f8e308a4 also invalidate fw2 when invalidating fw 2016-09-11 12:23:14 +02:00
Andreas Shimokawa 67bfe2b81e add hint about withdrawn releases 2016-09-11 00:59:21 +02:00
Andreas Shimokawa ec1f539267 revert accidentially commited stuff 2016-09-11 00:38:26 +02:00
Andreas Shimokawa 053b9553bc correct changelog (pebble health is also affected by userid and deviceid swapping) 2016-09-11 00:36:04 +02:00
Andreas Shimokawa 57a9a7ab0b bump version, update changelog 2016-09-11 00:18:46 +02:00
Andreas Shimokawa 9c2e40ecc0 Pebble: fix mixup of userId and deviceId in contructor for Misfit and Morpheuz samples 2016-09-11 00:12:37 +02:00
Andreas Shimokawa e1927733ba remove special handling for conversations (latest release removed pebble messages) 2016-09-10 23:53:24 +02:00
Andreas Shimokawa dcff1f840c update MPAndroidChart to v2.2.5 2016-09-10 23:53:24 +02:00
cpfeiffer bfffd64b65 Even more service testcase cleanup 2016-09-10 11:22:26 +02:00
cpfeiffer c31049839a Make DeviceCommunicationServiceTestCase runnable with robolectric
- enables the test for travis
- tests operation when not connected
- tests connecting
- tests operation when connected
2016-09-10 11:12:51 +02:00
Andreas Shimokawa ee0f1f23c0 remove maven crap 😈 2016-09-09 17:44:16 +02:00
Andreas Shimokawa 0bab0b1cfa remove TODO.md and update README 2016-09-08 22:39:18 +02:00
Andreas Shimokawa d5e31451b4 update translations from transifex (Italian and Japanese). THANKS! 2016-09-08 08:37:49 +02:00
Daniele Gobbetti ea39bb9c09 TODO: indent the bullets correctly 2016-09-07 21:17:18 +02:00
Daniele Gobbetti b9d3028d3f Added a some testing results 2016-09-07 21:12:21 +02:00
Andreas Shimokawa 9fbd8688c8 update translations from transifex (thanks) 2016-09-07 11:33:04 +02:00
cpfeiffer ec0a0db4f6 Make strings translatable in the database management activity 2016-09-06 22:59:08 +02:00
Andreas Shimokawa 8c1577a478 update japanese translation from transifex (thanks!) 2016-09-06 22:19:19 +02:00
cpfeiffer da18f661fe updated 2016-09-06 22:05:14 +02:00
cpfeiffer d011c437a2 This hopefully fixes the recreation of the old activity db
(I cannot reproduce or test it)
2016-09-06 21:54:03 +02:00
cpfeiffer f2b344349f Make some primary and foreign keys not-nullable 2016-09-06 00:00:48 +02:00
cpfeiffer afef50dfab Hide the "merge old activity database" elements when there is no old db 2016-09-05 23:55:00 +02:00
cpfeiffer 9928ad80c4 Update AS 2.2 RC 2016-09-05 23:10:00 +02:00
Andreas Shimokawa 051c617f75 update changelogs 2016-09-05 09:27:57 +02:00
Andreas Shimokawa c901fa2a5b update German from transifex 2016-09-05 08:51:36 +02:00
Andreas Shimokawa 3c6bc9051a update Spanish from transifex (thanks!) 2016-09-05 08:31:25 +02:00
cpfeiffer cd84b891d9 micro cleanup 2016-09-05 00:17:34 +02:00
cpfeiffer ce175c2952 Test SampleProvider (MiBand for a start) 2016-09-05 00:10:38 +02:00
cpfeiffer 2e91246a45 Make sure that every Test class has at least one @Test method, or is abstract 2016-09-04 22:52:55 +02:00
cpfeiffer 411a90326e Fix method name 2016-09-04 22:39:35 +02:00
cpfeiffer 2c27f30575 Some testcase infra 2016-09-04 22:10:35 +02:00
cpfeiffer 549c05cd0d Remove a slightly too technical entry 2016-09-04 21:44:28 +02:00
cpfeiffer 7c1ddc7f82 Added Mi changelog + some rewordings 2016-09-04 21:20:42 +02:00
Andreas Shimokawa f030a1bdea update italian from transifex. Thanks Daniele ;) 2016-09-04 18:37:06 +02:00
Daniele Gobbetti aa2d37c76b Small changes to the DB management activity #UX 2016-09-04 16:20:50 +02:00
Daniele Gobbetti 5cbedc782d Pebble health: accept records of firmware 4.0 (nothing changed among the data we parse) 2016-09-04 15:59:19 +02:00
Andreas Shimokawa 41b20b8c57 update French translation from transifex (thanks!) 2016-09-04 11:28:12 +02:00
cpfeiffer 8e154ca67d slightly more testing of Device/Attributes 2016-09-03 21:16:45 +02:00
Daniele Gobbetti 044c9ed101 Update CHANGELOG.md
Add mention of the fact that data migration is not automatically performed as first point.
Added database management activity
Fix typo and add clarification about data migration
2016-09-02 12:10:56 +02:00
Andreas Shimokawa 36b03e92b3 update changelog (in progress, incomplete) 2016-09-02 11:57:58 +02:00
Andreas Shimokawa 5a49f1215e Display known devices from db even if Bluetooth is off or unsupported
This allows to view activity data without turning bt on or connecting to the pebble emulator via tcp/ip (needs modified manifest for internet access)
2016-09-02 10:54:32 +02:00
cpfeiffer 6f02f9e350 Unit-Test and fix *Attributes sort order
When adding e.g. new DeviceAttributes, we cannot simply add them to the
attributes list, because that list is supposed to be ordered. We could
insert it at the beginning, but that would be brittle to changes in the
GBDaoGenerator, so we simply re-fetch them from the db after adding
new attributes.
2016-09-02 00:34:30 +02:00
Daniele Gobbetti 507338d034 TODO: One down, but two more to go. 2016-08-31 17:36:01 +02:00
Daniele Gobbetti 1e6cb67edd UI refactoring of the DB management activity.
Added a method to delete the legacy DB only.
2016-08-31 17:35:28 +02:00
Daniele Gobbetti e230bd1d07 Fix reference to the DB management activity 2016-08-31 15:27:31 +02:00
Daniele Gobbetti 6a2043eeb7 Create DB management activity by taking the existing functionalities from the debug activity. 2016-08-31 15:12:26 +02:00
Daniele Gobbetti 84e644fa1a Add preference for save raw pebble health data. 2016-08-31 14:05:02 +02:00
cpfeiffer cd535a0a45 Include known devices from the database in CC
(as long as device-support is still available)
Fixes non-paired devices not being displayed
2016-08-31 00:33:54 +02:00
cpfeiffer 6340bcff15 Small cleanup 2016-08-31 00:03:20 +02:00
cpfeiffer d9283d0f22 Sigh. Fix LoggingTest on Travis
When running all tests from gradle, they are executed in a single VM,
and from a quick look it is not configurable to start LoggingTest in
a separate VM (in order to enforce fresh logback configuration).

Thus, previously started tests interfere with the custom logback
configuration of LoggingTest.

=> Set the logback configuration in advance in build.gradle
2016-08-30 01:25:43 +02:00
cpfeiffer b96f2ed301 Apparently that was too much output for travis... 2016-08-30 00:18:31 +02:00
cpfeiffer 29dc5daa43 try to get more output from travis 2016-08-30 00:06:34 +02:00
Andreas Shimokawa 031a683215 update russian translation from transifex (thanks!) 2016-08-29 23:54:33 +02:00
Andreas Shimokawa 854b925c17 add hint about importing data later in onboarding activity 2016-08-29 23:46:33 +02:00
cpfeiffer 7c2bc3804c updated 2016-08-29 01:01:06 +02:00
Andreas Shimokawa 93b165ee96 onboarding: use the text area not the button itself for displaying the info text ;) 2016-08-29 00:32:36 +02:00
cpfeiffer bcb07ccacd Enable LoggingTest with robolectric 2016-08-29 00:26:29 +02:00
Andreas Shimokawa c93186cc56 fix name of onboading activity 2016-08-29 00:23:48 +02:00
Andreas Shimokawa 07ee860b1c add information to onboarding activity, make strings translatable 2016-08-29 00:17:40 +02:00
Andreas Shimokawa c55369747d launch onboarding (database migration) activity from service when:
- device got initialized
- is not yet in the new device db
- an old database exists
2016-08-28 23:49:05 +02:00
cpfeiffer eb7771c1a9 Support for overlapping ActivityDescriptions + testcases 2016-08-28 00:22:34 +02:00
cpfeiffer ca6b51b435 Updated TODO 2016-08-27 23:41:09 +02:00
cpfeiffer 39c7762416 Update the device in case it has changed 2016-08-27 23:25:37 +02:00
cpfeiffer 1a22259b4e Good practice: use the DAO to insert/update instead of the session
(saves a few cycles to look up the correct DAO from the session)
2016-08-27 23:12:48 +02:00
cpfeiffer 840a125c81 Fix compilation 2016-08-27 22:55:00 +02:00
cpfeiffer 8d6e6c8675 Upon request, delete not only the old, but also the new database 2016-08-27 22:51:00 +02:00
cpfeiffer ae2df2580c Remove/document deprecated stuff 2016-08-27 22:45:27 +02:00
cpfeiffer e139840fee More appropriate user handling: update the user instead of creating new 2016-08-27 22:34:30 +02:00
cpfeiffer c879e1c063 Update to gradle android plugin 2.2-beta3 2016-08-27 22:33:42 +02:00
cpfeiffer bfaaed7e5c Detcach samples from the session after querying, to save memory 2016-08-27 21:40:46 +02:00
cpfeiffer fb30321cca Add missing @Override and extract some constants 2016-08-27 21:17:34 +02:00
cpfeiffer 083d752011 Even more documentation/specification 2016-08-27 20:14:42 +02:00
cpfeiffer c2a509be74 Updated TODO 2016-08-27 17:22:55 +02:00
cpfeiffer ec9e999be1 Lots of documentation 2016-08-27 16:44:47 +02:00
cpfeiffer ec0db033b1 Avoid potential NPE when subclasses return null for rawKindSampleProperty 2016-08-27 15:36:42 +02:00
cpfeiffer 350e72d534 Initial support for user-configured activity descriptions
- for a given user and time span, there may be an ActivityDescription
  - with a textual description
  - and a list of tags
- every tag has
  - a name
  - and an optional description
2016-08-27 00:25:45 +02:00
Andreas Shimokawa 5ab40918c0 rename database from test-db5 to Gadgetbridge 2016-08-26 23:48:54 +02:00
Andreas Shimokawa 34aead6c63 remove obsolte stuff 2016-08-26 23:37:17 +02:00
cpfeiffer e81c1bdc28 Disable java7 build, instead set sourceCompatibility and targetCompatibility 2016-08-26 23:27:53 +02:00
cpfeiffer 20be49b717 Go back to gradle android plugin 2.2-beta1 (appears to work with java7) 2016-08-26 22:47:22 +02:00
cpfeiffer 770fa952d0 Update gradle to 3.0, gradle android plugin to 2.2-beta2
Also needs java8, hopefully only for compilation though
(we need java7 for KitKat)

This also fixes lots of exceptions in the pmd report.
2016-08-26 22:27:17 +02:00
cpfeiffer b5221eb276 Update todo 2016-08-26 21:16:36 +02:00
cpfeiffer 69f73467ea Store not only device type, but also the model in the db 2016-08-26 21:16:01 +02:00
cpfeiffer c59553c9c9 Rename GBDevice#hardwareVersion to model
(e.g. while DeviceType is "PEBBLE" in general, the model
might specify a Pebble Time or Time Steel.
2016-08-26 20:57:59 +02:00
Andreas Shimokawa 4363f110fb finish onboarding activity after import 2016-08-25 01:27:09 +02:00
Andreas Shimokawa 063d00cc51 only launch onboarding after device is initialized and write device to database if not in yet. 2016-08-25 01:08:43 +02:00
cpfeiffer 49b8b9ebca More robolectric stuff
- guard against multiple GBApplication.onCreate() invocations
- test DBHelper.getDevice() for a start
2016-08-25 00:00:53 +02:00
cpfeiffer 38c4be4379 Remove isEqual() in favor of Objects.equals() 2016-08-24 23:14:25 +02:00
cpfeiffer bfc0b4faaf Add robolectric dependency and addd a first EntitiesTest
Use KitKat (19) as target sdk since robolectric 3.1.2/sqlite4java
does not understand "WITHOUT ROWID" tables.

Also, add constants for user's gender and document some things.
2016-08-24 22:56:42 +02:00
cpfeiffer 02ac70e2a7 Only dismiss the dialog is the activity is still alive 2016-08-24 22:56:42 +02:00
cpfeiffer 24d342565b Remove some finished todos 2016-08-24 22:56:42 +02:00
Andreas Shimokawa ec4469a87b Testing with dummy onboarding activity
crashes
2016-08-24 20:15:26 +02:00
Daniele Gobbetti 2a2ad20aa3 Store raw data in the DB for later interpretation. 2016-08-21 20:24:02 +02:00
Daniele Gobbetti b617ba7264 Fix the logic regarding the last sample of a run. Remove the unneded try-catch block and reuse the current DB session instead. 2016-08-21 17:38:07 +02:00
Daniele Gobbetti 5a3a0495c9 TODO: Onboarding activity before new release 2016-08-21 17:14:02 +02:00
Andreas Shimokawa 0ae9955a6f Display Toast when trying to import old Misfit data (unsupported)
... and update TODO
2016-08-20 21:38:39 +02:00
Daniele Gobbetti 6119f3501a Import the old samples and map the ranges to overlays for pebble. 2016-08-19 21:09:32 +02:00
Andreas Shimokawa 3fb558c536 db refactoring: add raw data column in health overlay table and make rawType part of the composite key 2016-08-18 22:06:26 +02:00
Daniele Gobbetti 0126b90f20 Store the timestamps as sent by the pebble. Use a strict inequality operator at the end of the period to exclude the last sample. 2016-08-18 21:44:06 +02:00
cpfeiffer 7a16834482 *return* the created device! 2016-08-18 21:29:26 +02:00
cpfeiffer deeaa87df7 Batch the import of old activity samples to save memory during import 2016-08-18 20:51:15 +02:00
Andreas Shimokawa ce8af615d1 Merge branch 'composite-key-dao' 2016-08-18 20:35:56 +02:00
cpfeiffer 6e98defe94 Only import old activity data once per device, not for every provider 2016-08-18 20:29:20 +02:00
cpfeiffer fbf06c1fe3 Separate coordinator and support instances for Mi1 and Mi2 #323
+ Some more testing stuff for Mi2
2016-08-17 00:53:16 +02:00
cpfeiffer 26d490ffd6 Store the DeviceType in the Device entity
(so that we can later recreate a GBDevice from a Device)
2016-08-17 00:34:19 +02:00
cpfeiffer e0c52c7da5 Update gradle 2016-08-16 21:40:18 +02:00
cpfeiffer 9b7e8e06d6 Improved time conversion (0x2A0F)
- support for org.bluetooth.characteristic.local_time_information
- support for day of week in 0x2A2B

Unfortunately Mi Band 2 does not support 0x2A0F ;(
2016-08-15 00:40:35 +02:00
cpfeiffer 6843b5aa8f Add icon for Mi Band 2 2016-08-15 00:39:31 +02:00
cpfeiffer 8766fc5269 Handle MiBand2 device type 2016-08-14 23:25:35 +02:00
cpfeiffer a38bea892a Some logging of found devices/uuids 2016-08-14 23:21:09 +02:00
Andreas Shimokawa 4ddbbfdfb0 change db name to test-db5 2016-08-14 22:36:50 +02:00
Andreas Shimokawa 69933c5e92 db refactoring: depend on latest -fyg patched greendao, change column order again (primary keys first), remove index from pebble health overlay also 2016-08-14 22:33:41 +02:00
Andreas Shimokawa eb962c65f0 work towards composite keys
(builds but does not run because greendao generates wrong table creation code - we need to fix that)
2016-08-13 01:24:43 +02:00
cpfeiffer b9df746ea6 Add the missing word :) 2016-08-13 00:58:12 +02:00
cpfeiffer 7c060506cf Invalidate UserAttributes and DeviceAttributes when new ones are created 2016-08-13 00:52:35 +02:00
cpfeiffer b3984a409c Fix checking for up-to-date User and Device attributes 2016-08-13 00:27:38 +02:00
cpfeiffer 65d973401a Updated todo-list 2016-08-13 00:17:36 +02:00
Andreas Shimokawa f6629ad8e4 add TODO.md for 0.12.0 release
Oh sweet silence on the issue tracker
2016-08-12 23:07:15 +02:00
Andreas Shimokawa 4280e9612d Update README.md 2016-08-11 21:42:25 +02:00
Andreas Shimokawa 68b303246d db refactoring: change column order for health overlay table also 2016-08-10 23:26:25 +02:00
Andreas Shimokawa 359ed46b06 db refactoring: remove raw activity kind field from pebble health minute data, add a blob column for raw undecoded health minute data.
also:
- change column order for all pebble related minute data tables (mandatory stuff first, then custom columns, for easier addition of new columns)
- remove unused code

TODO:
- fix column order in Mi Band table also
2016-08-10 23:06:07 +02:00
Daniele Gobbetti 23c289ce1a Use the real raw values as received by the device.
Some types were also added, even though they are educated guesses.
2016-08-09 20:22:05 +02:00
Daniele Gobbetti 22d0387f76 Fix the comparison order again ,as it was correct originally.
Partial revert of 5cfddbb7e9
2016-08-09 20:05:42 +02:00
Daniele Gobbetti 4a7a34f461 Use only the overlay table for storing overlay data.
This removes the need to wait to have minute samples to store the overlay data, hence store has been made void instead of boolean.
2016-08-09 18:05:24 +02:00
Daniele Gobbetti 5cfddbb7e9 Use the right properties to build the query and fix the comparison operators.
Performance: iterate first on the smaller dataset.
2016-08-09 17:52:07 +02:00
Andreas Shimokawa fe5ec74ca1 Pebble Health: read overlay data in sample provider and patch in raw kind
completely untested
2016-08-09 11:56:05 +02:00
Andreas Shimokawa 5072d6b959 Pebble: try to write to health activity overlay table 2016-08-08 19:16:53 +02:00
Andreas Shimokawa b708ad942e db refactoring: add proposed schema for pebble health overlays 2016-08-08 09:01:36 +02:00
Andreas Shimokawa af58b4600d whoops 2016-08-07 12:15:40 +02:00
Andreas Shimokawa c4f83d68cd refactoring: add generic support for manually filtering samples by acticty kind in AbstractSampleProvider
This allows a lot of simplifications for Morpheuz and Misfit
2016-08-07 11:45:09 +02:00
Andreas Shimokawa 6b2565e4c9 DB refactoring: remove activity type from Morpheuz database, determinate it in PebbleMorpheuzSampleProvider instead 2016-08-07 01:47:15 +02:00
Andreas Shimokawa e05d40dc7e Pebble: Support for latest version of Morpheuz (4.6)
Also a few simplifications and minor fixes.
Morpheuz 3.7 should still work (last version for FW 2.x)
2016-08-06 01:19:38 +02:00
cpfeiffer a7b9ae5596 whitelist fw 4.15.12.10 #369 2016-08-04 00:05:58 +02:00
Andreas Shimokawa 43f3913669 update Japanese, Ukranian and Spanish from transifex (thanks!) 2016-08-02 00:43:37 +02:00
Andreas Shimokawa 9520e23439 fix negative steps in Charts with Morpheuz 2016-08-02 00:31:29 +02:00
cpfeiffer 43d7566c0b some more microsteps #206 2016-08-01 22:18:57 +02:00
Andreas Shimokawa 4fe498efc2 Pebble: delay 100ms after writing a pebble packet to the output steam
This fixes a problem on newer firmwares, probably from 3.0 on, where sending an
appmessage packet right after acknowledging a previous incoming appmessage
packet results in our outgoing appmessage packet to be NACKed by the pebble
firmware and not even reaching the app running on the pebble.
2016-07-31 23:49:19 +02:00
Andreas Shimokawa 8ba1ae3f3e create extra table for Morpheuz, remove more unused stuff
Pebble activity tracker now do not share a common base anymore.

TODO: consider creating a custom way of querying activity types like in Misfit. The activity kind stored in the database now is solely based on the intensity, so it is redundant.

(#206)
2016-07-31 00:06:26 +02:00
Andreas Shimokawa eabe625c47 rename some classes, remove obsolete ones 2016-07-30 23:22:27 +02:00
cpfeiffer b43b7948b0 Let GallCallback return boolean values in order to mark an event as "consumed"
(to avoid dispatching the event to further listeners (ble profiles)
2016-07-28 23:04:37 +02:00
cpfeiffer c9a9566dad Minor fixlet 2016-07-28 22:42:16 +02:00
cpfeiffer 493444a2a0 Remove unused stuff 2016-07-28 22:28:29 +02:00
cpfeiffer b22111df9d Fix field ordering of ActivitySample (c'tor!) and improve importer #206 2016-07-28 22:12:20 +02:00
Andreas Shimokawa 8ea29e6e1d Refactor database / sample access (#206)
We now have separate tables for each provider's samples but a common interface.
2016-07-27 23:34:13 +02:00
Carsten Pfeiffer bce7a6c406 Merge pull request #362 from Ivan4537/patch-1
Update strings.xml
2016-07-27 00:15:31 +02:00
Andreas Shimokawa dd5c80c2e7 forgot to add file 2016-07-25 22:28:40 +02:00
Andreas Shimokawa 726f767576 work towards a Pebble Misfit raw sample table 2016-07-25 22:19:39 +02:00
cpfeiffer f5ba09ebe0 Some babysteps towards miband2 support #323
Start to implement standard BLE profiles/services.
2016-07-25 00:00:22 +02:00
Ivan fd1e0e5648 Update strings.xml 2016-07-22 20:13:08 +03:00
cpfeiffer df59ce7b96 Switch light sleep and deep sleep #250
Apparently REM is considered deep sleep and NREM is considered light
sleep even though NREM (non-rapid-eye-movement) phase 3 is actually
defined as deep sleep.
2016-07-19 20:43:28 +02:00
cpfeiffer 1997a9b7fa some more service discovery logging 2016-07-18 23:55:44 +02:00
Carsten Pfeiffer c3d7b4a7cf Merge pull request #357 from rplevka/rplevka-srings
Fixed 'Activiy' typo in strings.xml
2016-07-15 20:49:32 +02:00
cpfeiffer 802314fc13 Updates for 0.11.2 release 2016-07-15 00:48:50 +02:00
cpfeiffer 7b26986ab0 Fix for #349
We must not use UUID_CHAR_PAIR anymore. This prevents connecting
without being bonded. Connecting when bonded still works.

As without bonding, ControlCenter would not display the device anymore,
we have to re-install the "remember last connected device" in the
preferences thing.
2016-07-15 00:13:49 +02:00
Roman Plevka e8a4c28510 Fixed 'Activiy' typo in strings.xml 2016-07-14 23:15:36 +02:00
cpfeiffer ebda3e1535 uncomment some constants 2016-07-14 20:37:20 +02:00
cpfeiffer 367091587f No more 0x8 in the logs
It's the confirmation that setting the latency succeeded.
2016-07-14 20:15:54 +02:00
cpfeiffer aa00d2f93a Avoid NPEs when device-name is null 2016-07-12 00:24:23 +02:00
cpfeiffer 76895aa2b1 Register to device name and alias changes and update accordingly 2016-07-11 00:28:15 +02:00
cpfeiffer 80930ce42a More logging for pairing, destroy pairing activity when bonding failed #349 2016-07-10 00:11:28 +02:00
cpfeiffer eb7b4be986 No restart necessary after enabling logging 2016-07-10 00:11:28 +02:00
Andreas Shimokawa 340a0f4a66 Fix coordinators not recognitzing devices by name
Do not ask a device candidate for its name , ask the underlying BluetoothDevice
The candidate uses the device alias - not good for matching
2016-07-09 23:10:21 +02:00
cpfeiffer f54163faeb centralize quit() functionality in GBApplication 2016-07-08 22:35:52 +02:00
cpfeiffer 9215233344 Whitelist 4.16.3.7 Mi1S firmware 2016-07-08 22:17:19 +02:00
cpfeiffer 8154a887cb When there are cached services, skip service discovery 2016-07-08 22:15:36 +02:00
cpfeiffer ce47f62c5b Missed a word in changelog 2016-07-08 22:01:01 +02:00
cpfeiffer 31c9d7ed3b Updated changelog for 0.11.1
(cherry picked from commit 563af6d0175b5ff415085fe562f4cf03ab364098)
2016-07-06 23:58:21 +02:00
Andreas Shimokawa 8ea0fa46fb update spanish translation from transifex (THANKS!) 2016-07-06 22:36:29 +02:00
Andreas Shimokawa 26bab26917 bump version update changelog 2016-07-06 22:35:41 +02:00
Andreas Shimokawa 4de45787c3 Properly acquire network location if last location is not known
A toast will be shown if the network location provider is disabled.
Location will be automatically acquired after enabling it.

Fixes #346
2016-07-05 23:56:14 +02:00
cpfeiffer 20d8732d10 In GBDevice.toString(), return the correct state instead of the simplified one 2016-07-05 23:36:21 +02:00
cpfeiffer 154b7d28bb Let discovery activity also display device aliases 2016-07-05 23:36:21 +02:00
Andreas Shimokawa 903890067d Settings: properly check the input type flag
Fixes a bug where latitude/longitude could be made empty
2016-07-05 23:14:48 +02:00
cpfeiffer 94cc1a883a Use GBActivity in Pairing Activity for theming 2016-07-05 22:13:11 +02:00
cpfeiffer 3bb1a228ec Fix crash during device discovery 2016-07-05 21:48:33 +02:00
Andreas Shimokawa 43f95aee9c fix NPE 2016-07-05 21:47:51 +02:00
cpfeiffer 9ae69eac55 Avoid NPEs when BluetoothDevice.getName() returns null 2016-07-05 21:03:30 +02:00
cpfeiffer 9881b6c281 Do not display paired devices in the discovery activity 2016-07-05 20:46:47 +02:00
cpfeiffer abeb642972 Don't remember paired Mi device in preferences
(this was just a very old workaround for an even older Mi firmware
that couldn't pair)
2016-07-05 20:27:23 +02:00
cpfeiffer 8549031c6f Only attempt to reconnect when we were previously initialized 2016-07-04 23:38:25 +02:00
cpfeiffer 91d1cea51f Avoid potential NPEs 2016-07-04 22:40:24 +02:00
Andreas Shimokawa 73b2fc357e Pebble: Add some Pebble Time 2 support - NOT WORKING
Not working because these are expected to use BLE.
The Emulator should work though.
2016-07-04 22:09:56 +02:00
Andreas Shimokawa 966b9abb87 preferences: set longitude/latitude inputType to "numberDecimal|numberSigned"
allows you to live west of 0°E ;)
Also pops up a better keyboard
2016-07-03 22:07:14 +02:00
Andreas Shimokawa a2c2e48719 fix NPE in settings when last known position is null
This is not a proper fix, we would have to request a location first.
I just had a last known position when testing before. :/

Issue #346
2016-07-03 21:29:02 +02:00
cpfeiffer 8b24e098ea Set sampleProvider to avoid NPEs 2016-07-01 00:22:54 +02:00
Andreas Shimokawa 9eb768ace0 update information about app management on FW 3.x 2016-06-29 23:58:47 +02:00
Andreas Shimokawa 09c1717e68 Revert "Remove authors and link to contributors.md instead"
This reverts commit 76dcb8f828.
2016-06-29 23:26:59 +02:00
cpfeiffer f65afa64d9 log raw activity data from mi band, closes #341
Hopefully aids in deciphering activity kinds.
2016-06-28 23:23:29 +02:00
cpfeiffer f0da25c49b Fix activity fetching getting stuck when double tapping #333 2016-06-28 23:07:24 +02:00
cpfeiffer 76dcb8f828 Remove authors and link to contributors.md instead 2016-06-28 22:16:44 +02:00
cpfeiffer 7613b62dab Improved discovery mechanism #323
Does not rely solely on mac addresses anymore. Should help when
mac address randomization is used.
2016-06-28 00:35:50 +02:00
cpfeiffer 76a44ad3a4 more logging to detect problems with external dirs: #343 2016-06-27 22:01:52 +02:00
cpfeiffer 56615de1f0 log FileNotFoundException when checking if directory is writable or not: #343 2016-06-27 21:29:39 +02:00
cpfeiffer e70a2290c3 sampleproviders now return device specific data #206 2016-06-27 20:43:48 +02:00
Andreas Shimokawa 358cd6df5e update German translation 2016-06-26 19:01:39 +02:00
Andreas Shimokawa 07283d4a75 update Japanese from transifex (thanks!) 2016-06-26 18:00:18 +02:00
Daniele Gobbetti 69be5dbbc7 Set the json directly instead of using parseconfig
Some watchfaces do some processing on the incoming json, and we are storing the json after the fact, and double parsing isn't good.
2016-06-26 14:13:47 +02:00
Daniele Gobbetti 1430619c30 Implement watchapp configuration presets.
This is a quick & dirty solution that may be improved by storing multiple presets in the DB in the future.
2016-06-26 11:55:08 +02:00
Andreas Shimokawa 248e38b5ef add big fat hint about Pebble 2 and Pebble Time 2 2016-06-25 21:29:28 +02:00
Andreas Shimokawa 339eaf05aa update german translation 2016-06-25 19:51:55 +02:00
cpfeiffer 2fa166e381 Fix potential NPE, setup environment earlier 2016-06-25 18:58:54 +02:00
Andreas Shimokawa 0209b1b403 add translations from transifex (thanks!) 2016-06-25 18:26:44 +02:00
Andreas Shimokawa b5cf2b20be Pebble: In App Manager, enable adding of "Send Text" system app on non-aplite platforms
The App is not functional with Gadgetbridge, but since it is visible on the watch when canned messages for call dismissal is enabled, we support sorting it.
2016-06-25 18:08:53 +02:00
Daniele Gobbetti 20e2846d00 Enable localization of appmanager tab strip 2016-06-25 11:27:56 +02:00
Daniele Gobbetti 0f0a7ea925 Add send sms system app (commented because it only appears on Time series Pebbles after setting up SMS call decline) 2016-06-25 11:19:01 +02:00
Andreas Shimokawa 0a1ef37c14 bump version, update changelogs 2016-06-24 21:51:02 +02:00
Andreas Shimokawa 181df7311a add missing file 2016-06-24 14:57:01 +02:00
Andreas Shimokawa 659165fa4c Pebble: fix new app manager on 2.x
- properly intert apps reported from pebble into the corresponding tab
- disable tracking of installed apps
- disable drag and drop for apps and watchfaces
- ...
2016-06-24 13:39:29 +02:00
Andreas Shimokawa 1de6ee019f Pebble: fix regression. FW 2.x was completely broken
No one noticed, so it seems we can kill 2.x support soon :D
2016-06-24 12:07:44 +02:00
Andreas Shimokawa b77f3ad3bf Pebble: Implement call dismissal with canned messages
This also moved the canned replies setting to pebble settings
(we will change that if we have another device supporting this)
2016-06-24 10:25:08 +02:00
Andreas Shimokawa 7ed867a88d Merge pull request #338 from geripgeri/hungarian-translation
Hungarian translation improved.
2016-06-22 18:05:07 +02:00
Gergely Peidl 5131d50617 Hungarian translation improved. 2016-06-22 10:42:35 +02:00
Andreas Shimokawa 67e5bc0434 Pebble: implement decline call with SMS
Based on a proposal by @danielegobbetti, thanks!

This still does not enable the feature since the necessary blobdb is not filled yet
2016-06-21 00:31:53 +02:00
Andreas Shimokawa 72dff2abd2 provide haptic feedback on long press in AppManager 2016-06-20 17:50:19 +02:00
Andreas Shimokawa 1a9c40e790 fix crash when rotating screen in AppManager 2016-06-20 17:42:32 +02:00
cpfeiffer 45fa930ac3 Properly check support for heartrate measurements 2016-06-19 23:22:28 +02:00
cpfeiffer 8772631087 Merge branch 'db-refactoring' 2016-06-19 22:53:09 +02:00
Andreas Shimokawa 4347f134d6 Pebble: add Music, Alarms, Notifications and Watchapps to System Apps
They can be ordered too!
2016-06-19 21:48:07 +02:00
cpfeiffer 9772d8af06 Merge remote-tracking branch 'origin/master' into db-refactoring 2016-06-19 21:14:03 +02:00
Andreas Shimokawa 7597ce337d Pebble: do not move to last position if reinstalling an app, the order seems to be preserved on the pebble 2016-06-19 21:13:47 +02:00
Andreas Shimokawa 24e840e03b Pebble: remove "move to top" feature completely 2016-06-19 21:06:38 +02:00
Andreas Shimokawa 4b5969ef96 Merge branch 'master' into appmanager-fragments 2016-06-19 12:33:05 +02:00
Andreas Shimokawa f42899d910 Pebble: on FW 2.x do not send extended music info and do not send playstate
I don't think they are supported and I cannot test.
2016-06-19 12:17:19 +02:00
Andreas Shimokawa b2bae26d7d bump version, update changelog 2016-06-19 11:58:13 +02:00
Andreas Shimokawa 64182941d0 Pebble: always hide move to top feature in AppManager, it was not working as expected
A proper solution is in appmanager-fragments branch
2016-06-19 11:48:29 +02:00
cpfeiffer 7aa900ce82 Workaround crash in debug activity
(when progress dialog is visible and changing orientation)
2016-06-19 00:53:53 +02:00
cpfeiffer 0596c80381 Some migration fixes
especially: add unique index on samples using timestamp and device id
(since composite primary keys are not fully supported yet)
2016-06-19 00:40:51 +02:00
cpfeiffer 04c8a17d6e Merge remote-tracking branch 'origin/master' into db-refactoring 2016-06-19 00:12:02 +02:00
cpfeiffer 5607b1c892 Working merging of activity data, with (indeterminate) progress dialog 2016-06-19 00:10:47 +02:00
cpfeiffer dc932355b5 Some javadoc re DeviceManager 2016-06-18 23:40:37 +02:00
cpfeiffer 233a6155cc Add class DeviceManager that provides access to the list of managed devices
Basically moved code out of ControlCenter to a separate class. Also provides
change events when the device list has changed, or changes to the device
state have occurred.
2016-06-18 23:35:34 +02:00
Daniele Gobbetti 988f5ef1b2 Go back and show Toast if the watchapp doesn't really support configuration. 2016-06-18 21:17:38 +02:00
Daniele Gobbetti ad3f7e53b3 Try hard to hide the browser activity from the stack. 2016-06-18 21:08:51 +02:00
Daniele Gobbetti 245b8655e7 Fixed typo #251 2016-06-18 21:01:32 +02:00
Daniele Gobbetti 6749c493b1 Changelog for app configuration 2016-06-18 19:13:03 +02:00
Daniele Gobbetti 7263307409 Merge remote-tracking branch 'origin/master' into appmanager-fragments 2016-06-18 19:00:23 +02:00
Daniele Gobbetti 966c3d4811 Various improvements to the configuration page:
- localstorage is now cleared at every launch: this prevents some clay configuration pages to send back to the watch a number of keys that were set by other configuration pages
- only execute JS on document ready: this prevents some race conditions
- added dummy getTimelineToken function to Pebble JS object
- corrected (hopefully!) a few logic errors in the JS code (this referenced where it wasn't)
- refactored the steps visualization in JS
- lifecycle changes to the java activity: now the configuration page gets closed as soon as the settings have been sent, and there is only one instance of it
2016-06-18 18:40:57 +02:00
Daniele Gobbetti fffeb87607 Use constant instead of hardcoded string 2016-06-18 18:33:35 +02:00
cpfeiffer 2890fd6737 Merge remote-tracking branch 'origin/master' into db-refactoring 2016-06-18 01:27:19 +02:00
cpfeiffer 41e6833b2d Getting closer... db migration almost works. 2016-06-18 01:26:36 +02:00
Andreas Shimokawa 22b4e15988 Merge pull request #335 from geripgeri/hungarian-translation
Hungarian translation added. :)
2016-06-17 23:03:28 +02:00
Andreas Shimokawa e8f2a0bc9f Merge branch 'master' into appmanager-fragments 2016-06-17 22:47:17 +02:00
Andreas Shimokawa 79b439da28 Implement App Sorting
- grab icon to move apps
- cache can be sorted but nothing will be send to watch
- if sorting apps or watchfaces, order will be sent to watch
- we try to keep track of what is installed and what not

Firmware 2.x is currently not working properly
2016-06-17 22:43:06 +02:00
Daniele Gobbetti d5586478f3 Support legacy configuration pages #251
There are pages that do not honor return_to get parameter. This commit allows the user to enter the returned url manually.
2016-06-17 17:47:13 +02:00
Gergely Peidl 33d8ea2f56 Hungarian translation added. :) 2016-06-17 14:48:10 +02:00
cpfeiffer 13959677af More WIP for intrgrating old activity db into new one
(one demand to keep user in control)
2016-06-17 00:07:50 +02:00
cpfeiffer d544509b60 Merge remote-tracking branch 'origin/master' into db-refactoring 2016-06-16 21:55:16 +02:00
cpfeiffer 687beee501 Migration work 2016-06-16 21:54:53 +02:00
Andreas Shimokawa 65ac4b364f Merge branch 'master' into appmanager-fragments 2016-06-16 01:09:35 +02:00
Andreas Shimokawa 9f61458790 Pebble: Allow installation of aplite apps on diorite 2016-06-16 00:59:15 +02:00
Andreas Shimokawa b79b94809a initial support for FW 4.0-dp1 2016-06-16 00:24:27 +02:00
Andreas Shimokawa 1c6c78507c Merge branch 'master' into appmanager-fragments 2016-06-15 22:53:41 +02:00
Andreas Shimokawa d225743d64 Pebble: some preparations for Pebble 2 2016-06-15 22:53:05 +02:00
Andreas Shimokawa 7937fd6ea7 Merge branch 'master' into appmanager-fragments 2016-06-15 22:36:06 +02:00
Andreas Shimokawa 7690ad3af6 make appmanager usable again 2016-06-15 22:29:30 +02:00
Daniele Gobbetti 4120d686b8 Some key events were not working with the default audio player. Reported in #322 2016-06-15 22:15:21 +02:00
Andreas Shimokawa b5693bcb45 play around with drag and drop list 2016-06-15 19:56:34 +02:00
cpfeiffer 71d99384c1 Cleanup, remove duplicated code, fix some TODOs 2016-06-14 23:10:35 +02:00
Andreas Shimokawa 4895704f99 add missing file 2016-06-14 22:55:38 +02:00
cpfeiffer a01507a924 Merge remote-tracking branch 'origin/master' into db-refactoring 2016-06-14 20:14:04 +02:00
cpfeiffer 61957d6cb0 WIP: more db work 2016-06-14 20:13:08 +02:00
Andreas Shimokawa 3418543c31 play around with fragments 2016-06-14 20:04:54 +02:00
Andreas Shimokawa 1d6a697000 playing around with fragments 2016-06-14 15:56:38 +02:00
Andreas Shimokawa 98999993e5 Pebble: In AppManager allow moving apps on the device to the top (context menu) 2016-06-12 01:20:12 +02:00
Andreas Shimokawa f20b659b86 update changelog again 2016-06-11 23:45:14 +02:00
Andreas Shimokawa f812fb1b1f Improvements to MusicPlayback receiver
- Also send duration if "duration" extra is present
- If "playing" and "postion" extras are present send a music state update

treat previous state and current state as equal if position delta is <=2 seconds
(Neccessary for some players which update every second - the pebble however counts by itself)
2016-06-11 23:37:03 +02:00
Andreas Shimokawa 2d080cabb2 fix NPE by using Objects.equals() 2016-06-11 22:32:38 +02:00
Andreas Shimokawa d1a62968f6 Do not send new audio metadata to device if nothing has changed
This prevents some players which send a metadata changed intent every second to drain the battery
2016-06-10 23:13:33 +02:00
Andreas Shimokawa 8d3bd494b4 fix broken xml tag 2016-06-10 22:45:38 +02:00
Andreas Shimokawa 771ff7b2be bump version, update changelog 2016-06-10 22:39:00 +02:00
Andreas Shimokawa 26ca526fdd update gradle plugin 2016-06-10 22:23:06 +02:00
Andreas Shimokawa 6de002c88b also try to get track number and number of tracks from notifications 2016-06-10 22:20:55 +02:00
Andreas Shimokawa 243250f41f update translations from transifex (thanks!) 2016-06-10 00:08:00 +02:00
Andreas Shimokawa 66b5a21cf2 also gather music info from notifications when screen is off 2016-06-10 00:03:48 +02:00
Andreas Shimokawa b0fe4b1519 also gather music info from notifications when screen is off 2016-06-09 23:39:00 +02:00
Andreas Shimokawa 9623449b6e Merge branch 'master' into feature-weather 2016-06-09 23:15:23 +02:00
Andreas Shimokawa b76619bb5b Pebble: implement app reordering in PebbleProtocol
Not yet used.
2016-06-09 23:14:40 +02:00
Andreas Shimokawa fd31bfe56b Merge pull request #325 from stepardo/steffen/pocketcasts_notifications
Set extended music info by dissecting notifications on Android 5.0+
2016-06-09 23:12:17 +02:00
Steffen Liebergeld c5262869d9 Use names for playstates
These names need to be mapped to device specific constants in the
device code.
2016-06-09 20:02:55 +02:00
Steffen Liebergeld 91f374edec Revert ""duration" parameter in onSetMusicInfo uses microseconds"
The decision on granularity of APIs is up to the maintainers.

This reverts commit 204748c518.
2016-06-09 20:02:55 +02:00
Andreas Shimokawa 088dfda5f4 Pebble: implement app reordering in PebbleProtocol
Not yet used.
2016-06-09 19:55:36 +02:00
Steffen Liebergeld 204748c518 "duration" parameter in onSetMusicInfo uses microseconds
This is in line with Android, and saves some calculations (and thereby a
tiny little bit of battery life).
2016-06-08 20:43:46 +02:00
Steffen Liebergeld fb71cdf55b Add handling for media session notifications
Since Android 5.0, media players can have interactive notifications that
reside in the notification area, and offer up to 5 control buttons
(play/pause, next, previous, etc), and information about the currentlu
playing media file.

We use these notifications to get information about the currently
playing media file such as:
- artist
- track (title)
- album
- duration (length of the media file)
- play state (playing, paused, stopped)
- position
- play rate (how fast is the media file being played)

We then send this information up to the device.

On Pebble, the music app will display the title and the artist, as
well as a progress bar showing the current position. The progress bar is
animated when the media file is being played, and if it is being paused,
it displays a pause symbol.

This code will be skipped when GadgetBridge is run on a device with
Android version older than 5.0 (lollipop).
2016-06-08 20:41:43 +02:00
Steffen Liebergeld 73fbaf0a54 Restore previous working of the debug activity
The previous commits broke the debug activity's setting of the music
info. This commit restores this functionality.
2016-06-08 20:32:34 +02:00
Steffen Liebergeld e386d6da43 Add onSetMusicState(MusicStateSpec stateSpec)
This commit contains the infrastructure needed for the
NotificationHandler to send music state information to the device. That
is, it introduces a call onSetMusicState(MusicStateSpec stateSpec), that
in turn sets up an intent to the service, which will then call the
encodeSetMusicState() function of the device. encodeSetMusicState is
available for pebble only. There are empty stubs for other devices.
2016-06-08 20:31:48 +02:00
Steffen Liebergeld 1d5c8bae9d MusicStateSpec: introduce new class describing the music state
Contains:
- state
- position
- playRate
- shuffle
- repeat

This is close to what PebbleProtocol currently supports.
2016-06-08 20:22:56 +02:00
Steffen Liebergeld 0470731e4b PebbleProtocol: Do not call encodeSetMusicState in encodeSetMusicInfo
encodeSetMusicState will be accessible on its own. If it was used to set
the music state, a call to encodeSetMusicInfo must not reset this info
arbitrarily.
2016-06-08 20:16:33 +02:00
Andreas Shimokawa 98a0774fc2 Merge branch 'master' into feature-weather 2016-06-07 22:53:40 +02:00
Andreas Shimokawa 32429df7bc Pebble: allow to enable or disable sync for each activity tracker in settings
This is useful if you have multiple phones and do not want to have your data synced to one of them
2016-06-07 22:51:14 +02:00
Steffen Liebergeld 389a143bdb Set music info for PocketCasts
PocketCasts tells about its current media state via notifications. This
patch tries to parse incoming notifications from PocketCasts and if
successful tells the device about it. Currently supported are track and
artist.
2016-06-07 19:34:37 +02:00
cpfeiffer ae548d0806 WIP: more work, compile again 2016-06-06 23:18:46 +02:00
cpfeiffer 3b87966fe9 Merge branch 'master' into db-refactoring 2016-06-06 22:16:40 +02:00
cpfeiffer 2b6ee41970 Add logging of heartrate values #318 2016-06-05 22:37:42 +02:00
cpfeiffer cb4dcf9fa6 Disable LoggingTest, fixes travis failures 2016-06-05 22:33:24 +02:00
cpfeiffer ca26e27c60 Basic support for Mi Band 2 #323, untested 2016-06-05 22:31:07 +02:00
Carsten Pfeiffer 1ed0dc59b2 Merge pull request #321 from pragmaware/mi-band-device-time-offset
Add support for shifting the device time by N hours
2016-06-05 22:29:44 +02:00
Andreas Shimokawa 0fb664c141 allow to switch languages at runtime 2016-06-05 14:33:09 +02:00
Andreas Shimokawa 9d3f3c57cd Pebble: make disabling of appmessage handlers more generic
Also disable morpheuz handler if morpheuz is not the chosen activity tracker
.... and bump reported version to 3.12 to match the latest pebble release
2016-06-04 21:50:26 +02:00
Andreas Shimokawa 321707af8f Pebble: ignore incoming misfit data if misfit is not set as the preferred activty tracker 2016-06-04 21:33:38 +02:00
Daniele Gobbetti 968d15c8d8 Keep the pebble health data on the pebble watch if the activity provider is not pebble Health.
This will nack all pebble health datalog messages. As mentioned in #322, this would allow to use multiple android device without secondary devices "sipping" the health data from the watch.
2016-06-04 18:21:49 +02:00
Daniele Gobbetti edb7471e0c Added a paragraph about questions
In the hope this helps for issue #319
2016-06-04 17:14:29 +02:00
Daniele Gobbetti 409097bc00 Merge pull request #320 from white-gecko/patch-1
Update README.md fix pebble wiki article
2016-06-03 17:41:19 +02:00
Natanael Arndt 8096cad626 Update README.md fix pebble wiki article
Update README.md fix the link to the pebble wiki article
2016-06-03 10:56:11 +02:00
Szymon Tomasz Stefanek 60fc29cc4d Add support for shifting the device time by N hours to allow for sleep data gathering of shift workers 2016-06-03 04:43:12 +02:00
Andreas Shimokawa df4ae49b72 update Japanese translation from transifex (thanks!) 2016-06-01 00:01:08 +02:00
cpfeiffer 2e6536555b Fix previous commit (compile!) 2016-05-31 22:56:22 +02:00
cpfeiffer 9a106667d2 Setting the wear location appears to fail for amazfit #274
So as a test, disable that for amazfit. Let's see what happens
next.
2016-05-31 22:33:38 +02:00
Andreas Shimokawa 19d7c03545 Pebble: get rid of log spamming when changing applications (unhandled message) 2016-05-31 14:18:45 +02:00
Andreas Shimokawa a9d74b52f8 update German übersetzung 2016-05-31 13:34:29 +02:00
Andreas Shimokawa 1dd0965ae1 update Italian traduzione from transifex 2016-05-31 13:07:11 +02:00
Andreas Shimokawa 9da050c51d update changelog 2016-05-31 13:05:55 +02:00
Andreas Shimokawa a15d07858e Sunset/Sunrise: always use UTC timezone for calendar operations 2016-05-31 13:03:15 +02:00
Daniele Gobbetti 42acb8915a Pass the integer keys to pebble, even if they are not within the known app keys. Requested in #251 2016-05-31 12:24:28 +02:00
Andreas Shimokawa 0231e83ea3 try to fix duplicate/missing surise/sunset pins 2016-05-31 00:07:24 +02:00
Andreas Shimokawa b71597800a Pebble: set device to reconnecting state when actively reconnecting
It makes it easier to watch connection attempts and the delay between them in control center.
2016-05-29 23:24:16 +02:00
Andreas Shimokawa f2cbee39f1 update changelog again
actually the last commit is responsible for the fix (even though it is not mentioned in the commit log)
2016-05-29 23:02:00 +02:00
Andreas Shimokawa 33da6c2925 Pebble: make sure that "waiting for reconnect" state is set when initial connect fails 2016-05-29 22:58:25 +02:00
Andreas Shimokawa 4533c80c95 make sure to only reconnect when device is in "waiting for reconnect state" 2016-05-29 21:44:43 +02:00
Andreas Shimokawa af14fb4f90 limit max charaters to longiute and latitude to 7 2016-05-29 21:40:56 +02:00
Andreas Shimokawa 2e8d96e995 add option to enable sunrise/sunset on the timeline
Also fix a small type Aquire -> Acquire
2016-05-29 21:29:27 +02:00
Andreas Shimokawa c9aad271da update translations from transifex (thanks!) 2016-05-29 20:53:25 +02:00
Andreas Shimokawa 2b88720f83 fix xml changelog and add missing entry 2016-05-29 20:50:05 +02:00
Andreas Shimokawa a13cd9d951 update changelog, bump version to 0.10.0
reasons for the bump are
- new permissing
- first contact with the timeline, although is is not very useful
2016-05-29 20:47:16 +02:00
Andreas Shimokawa 8970bbe044 display device address in info menu (IP:PORT / MAC) 2016-05-28 11:32:36 +02:00
cpfeiffer 2d49ce505a Set state to "Waiting for reconnect" for BTLE devices 2016-05-26 23:48:05 +02:00
cpfeiffer 50b7a02ef2 One more attempt at fixing dynamic logging reconfiguration
- moved out of GBApplication to class Logging
- the main thing is: when start()ing the FileAppender again, it *must*
- be configured to be non-lazy, otherwise it won't open the stream ever again.
2016-05-26 23:46:21 +02:00
cpfeiffer 6e33c7364a Remove some commented code 2016-05-26 22:21:58 +02:00
cpfeiffer c360eb3392 This should fix some connection problems #274
Some APIs have become synchronous, it seems, e.g.
connectGatt() -> onConnectionStateChanged() -> discoverServices() -> onServicesDiscovered()
appears to happen synchronously. So connectGatt() will not return before services are discovered!

So now we deal with this situation.
2016-05-26 19:03:38 +02:00
Andreas Shimokawa b0e0aec465 fix typo 2016-05-26 15:20:27 +02:00
Andreas Shimokawa 88f338b0b9 Reenable our discovery activity for Android 6
Now that we request location we are allowed to do a btle scan again
2016-05-26 14:58:36 +02:00
Andreas Shimokawa 7ef005f6a3 Allow getting the network location within pebble settings for sunrise/sunset calculation
It is also possible to set the location manually
On Android >=6 the required permission will be requested when pressing the button in settings.
2016-05-26 14:39:54 +02:00
Andreas Shimokawa fa6100fcec Merge branch 'master' into feature-sunrise 2016-05-26 13:11:03 +02:00
Andreas Shimokawa 31c15bb8b8 update changelog 2016-05-24 23:55:03 +02:00
Carsten Pfeiffer 145dbeedfa Merge pull request #315 from AnotherStranger/master
MusicPlayBackReceiver set track, artist and album to the artist membe…
2016-05-24 20:11:01 +02:00
andre bf66c25c7f MusicPlayBackReceiver set track, artist and album to the artist member of MusicSpec.
Now the assignment of artist, track and album is correct
2016-05-24 19:33:12 +02:00
Andreas Shimokawa 55a40f7b06 Sunset/sunrise: rotate ids and reuse the id from two days ago for tomorrow, this way we will have sunrise/sunset for 3 days while sending only sunrise/sunset per day 2016-05-24 14:46:22 +02:00
Andreas Shimokawa e3bee37b81 Change UUID prefix to 0x4767744272646700 2016-05-24 13:20:16 +02:00
Andreas Shimokawa cb1ec5dccb Make calendar event type byte instead of int
Now the UUID will be constructed like this:

High 64bit 0x4742474200 | type
Low 64bit id
2016-05-24 13:11:57 +02:00
Andreas Shimokawa c9c9b420dc Pebble: Send sunrise and sunset events to the pebble every day using AlarmManager 2016-05-24 11:19:57 +02:00
Andreas Shimokawa ec154c9041 Merge branch 'master' into feature-weather 2016-05-24 11:09:21 +02:00
Andreas Shimokawa af3cfefec0 Merge branch 'master' into feature-sunrise 2016-05-23 23:52:39 +02:00
Andreas Shimokawa 30c37d3172 Pebble: only remove apps from app list when they got deleted from cache also 2016-05-23 23:46:54 +02:00
Andreas Shimokawa 884c4262cf update changelog, bump version
(also change one German string and remove two newlines :P)
2016-05-23 23:37:57 +02:00
cpfeiffer 4504c5b5a4 Add Activity Description table and order samples 2016-05-23 23:31:22 +02:00
Andreas Shimokawa 24c51deaf9 Pebble: also delete other files from cache when deleting .pbw 2016-05-23 23:15:07 +02:00
Andreas Shimokawa f697906572 update Japanese and German tranlation from transifex (thanks!) 2016-05-23 22:03:21 +02:00
Daniele Gobbetti bef59ae9c0 Parse new version of Pebble health datalog message with tag "84".
This message was previously treated as a further "Sleep" message type, with firmware version 3.12 further types were added that are clearly unrelated to sleep (possibly to high-intensity activities like running etc.), hence the related code is now moved to a separate class.

The only decoded records are still sleep-related.

Fixes #312
2016-05-23 21:13:12 +02:00
Andreas Shimokawa 437de7f660 fix duplicate entry in strings.xml 2016-05-22 23:38:51 +02:00
Andreas Shimokawa efe5e546fd Merge branch 'master' into feature-sunrise 2016-05-22 23:36:09 +02:00
Andreas Shimokawa 80cf9fa8fe Pebble: allow to delete apps from pbw cache
Also remove delete entries from all system apps' context menus (not only health)
2016-05-22 23:32:25 +02:00
Andreas Shimokawa 30883ab244 Pebble: In AppManager mark cached apps with (C), installed apps on FW 2.x with (D) and (CD) if both is the case
Also always add pebble health as a system app to the list on devices that have it (no need to enable untested features for that anymore)
2016-05-22 22:48:45 +02:00
Andreas Shimokawa 5a20d7ec81 Pebble: no longer clear list of cached apps as soon as the installed watchfaces is received from Firmware 2.x
This will result in duplicate entries (first the whole cache, then actually installed watchfaces)
It might already make watchface configuation working on 2.x even though it is dirty

I wish everybody would just update to 3.x *sigh*
2016-05-22 12:30:47 +02:00
Andreas Shimokawa ca714417ac Pebble: also copy pbw to cache on Firmware 2.x
Neccessary step towards watchface configuration on 2.x
2016-05-22 11:33:14 +02:00
Andreas Shimokawa 6370fdbebe Merge branch 'master' into feature-sunrise 2016-05-22 01:25:52 +02:00
Andreas Shimokawa 0d7986a5ab Pebble: rework incoming reconnection support
This is now completely generic and should work for other devices also

Fixes #296
2016-05-22 01:19:28 +02:00
cpfeiffer d5cca84780 Small fixlets 2016-05-20 22:04:30 +02:00
cpfeiffer 0267ddb356 Merge branch 'master' into db-refactoring 2016-05-20 21:49:59 +02:00
cpfeiffer 400ae2bc3b Fix lint warnings using @TargetApi 2016-05-20 21:49:25 +02:00
cpfeiffer fa34cf9a17 Merge branch 'master' into db-refactoring 2016-05-20 21:42:30 +02:00
cpfeiffer a97efe1513 Updated contributors 2016-05-20 21:31:30 +02:00
Carsten Pfeiffer f933eb8fcd Merge pull request #306 from normano64/do-not-disturb
Detects if Do Not Disturb is in use
2016-05-20 21:21:08 +02:00
Normano64 31eabe9605 Fixed things based on feedback 2016-05-19 23:58:13 +02:00
cpfeiffer cfed531ad0 Add list of contributors 2016-05-19 22:33:27 +02:00
cpfeiffer 2e2030f67b Update translations from transifex, thanks! 2016-05-19 22:07:55 +02:00
Normano64 8a91628322 Detects if Do Not Disturb is in use.
Can handle sms and phone calls from priority senders when in Priority
only, but doesn't handle events and reminders.
2016-05-19 16:34:59 +02:00
cpfeiffer 4370be28b6 Remove some imports 2016-05-17 00:51:58 +02:00
cpfeiffer 75703b0dea Import and Export db are back 2016-05-17 00:51:00 +02:00
cpfeiffer 2d2df64003 Port schema migration to greendao 2016-05-16 23:54:51 +02:00
Andreas Shimokawa 907ad8f27a correction regarding deletion of old pins 2016-05-16 23:48:18 +02:00
cpfeiffer 4b374e3f7e Implement changeStoredSamplesType() 2016-05-16 23:45:21 +02:00
Andreas Shimokawa 4bd578ebea Pebble: send sunrise/sunset to watch for today and tomorrow, also delete previous timeline pins 2016-05-16 23:37:40 +02:00
cpfeiffer 876bdac918 More WIP: remove now unused code, better use of generics 2016-05-16 23:36:54 +02:00
cpfeiffer 40a376bbd0 Merge branch 'master' into db-refactoring 2016-05-16 23:01:16 +02:00
cpfeiffer 3e0bc16741 More WIP, but we're getting closer
current state:
- storing samples works (tested only mi band)
- charts work
2016-05-16 23:00:04 +02:00
Andreas Shimokawa 017f650b3f Pebble: send sunrine and sunset pins to timeline when setting time in debug menu
This is just a test and it will leak your timeline data, since we never delete it.
Also this adds some rough infrastructure for calendar events.
2016-05-16 17:30:11 +02:00
Andreas Shimokawa dafdb1008d Merge branch 'master' into feature-weather 2016-05-16 14:15:39 +02:00
Andreas Shimokawa 8c88223f26 update changelog 2016-05-15 23:29:19 +02:00
Andreas Shimokawa d66f842e9b Mi Band: Make sure live activity gets stopped when using the back button 2016-05-15 22:24:37 +02:00
cpfeiffer 3a1f91b5a8 Default to low latency mode #287
Tested with Mi1A and Mi1S -- works fine and faster than without
low-latency mode.
2016-05-15 00:22:53 +02:00
cpfeiffer 5963843216 Experimental support for #274 (untested yet) 2016-05-15 00:15:31 +02:00
cpfeiffer 6e44ddaee6 WIP: db refactoring 2016-05-15 00:09:34 +02:00
Andreas Shimokawa 5efe9a5eb8 update japanese and geraman translations form transifex (THANKS!)
I did not merge others because tx pull deletes stuff again
2016-05-14 12:23:01 +02:00
Andreas Shimokawa e2def7b467 update changelog, bump version 2016-05-14 12:15:48 +02:00
cpfeiffer 8ca821ab8a More WIP on db refactoring 2016-05-13 23:47:47 +02:00
cpfeiffer d0c8483d92 More WIP 2016-05-08 23:49:15 +02:00
cpfeiffer 9532fc879f "Ported" to AbstractSampleProvider 2016-05-08 23:17:05 +02:00
cpfeiffer 21cafa83d8 WIP: some more query work 2016-05-08 22:58:50 +02:00
cpfeiffer b805612ae5 Allow in-process dex with 2GB heap, as recommended 2016-05-08 21:39:55 +02:00
cpfeiffer 40837996f8 Fix logback initialization, closes #300 2016-05-08 21:39:23 +02:00
cpfeiffer 1a353239c4 Fix log content 2016-05-08 21:38:55 +02:00
Andreas Shimokawa 9b7f2c1e91 try to fix weiredness with pebble reconnects 2016-05-08 17:19:01 +02:00
Andreas Shimokawa 5b21895283 try to get BT alias name by reflection. Useful if you have a lot of Mi Bands 2016-05-07 21:46:20 +02:00
cpfeiffer cc5941f7eb Merge branch 'master' into db-refactoring 2016-05-05 10:19:19 +02:00
cpfeiffer 808e12d680 A little more WIP 2016-05-05 10:19:01 +02:00
Andreas Shimokawa 65a95366f4 Mi Band: Allow setting low-latency FW update mode in Mi Band development settings 2016-05-04 13:24:32 +02:00
Andreas Shimokawa 045d5119ff Do not update summary for checkbox preference
Was causing summary to get overwritten by "true" or "false" when changing preferences
2016-05-04 13:07:11 +02:00
Andreas Shimokawa 619a17425f Mi Band: Display hint about starting Activity Activity instead of App Manager
TODO: Fix the string, I have no idea how to properly name the Activity Activity
2016-05-04 12:31:29 +02:00
cpfeiffer 70eaca8883 Further WIP towards greendao:
- for now, use a custom version of greendao with the fix 39ac07be550c5f5b6fd265c8870f58015c95e908
- use a superclass for activity sample classes that provides value normalization using SampleProvider
2016-05-01 23:56:14 +02:00
cpfeiffer 827e10f49e Updated Mi Band features 2016-05-01 22:03:40 +02:00
cpfeiffer 4744d8b59e Some more WIP towards greendao. Note: does not compile atm. 2016-05-01 00:19:15 +02:00
cpfeiffer fc89194396 Update to greendao 2.2 2016-04-30 23:42:22 +02:00
cpfeiffer b15ffcbf15 Sigh -- looks like I will have to find a less intrusive way to change the application id 2016-04-29 23:24:12 +02:00
cpfeiffer 7d15d4ff42 Also update the package name in the manifest 2016-04-29 23:19:19 +02:00
cpfeiffer b363d08efb WIP: a little work towards greendao
need to think of how to integrate MiBandActivitySample and PebbleActivitySample
into the app. There's GBActivitySample, MiBandSampleProvider, PebbleSampleProvider,
etc.
2016-04-29 23:12:30 +02:00
cpfeiffer 403a14ce8d Use a different application id until the db refactoring is done
This allows installing both versions at the same time and avoids
data loss when the database is scrubbed.
2016-04-29 22:50:37 +02:00
cpfeiffer 64a6b9a936 Merge branch 'master' into db-refactoring 2016-04-29 22:28:53 +02:00
cpfeiffer 6863fababe Update changelog and prepare for 0.9.6 2016-04-29 22:07:16 +02:00
cpfeiffer 10d7274aa1 Fix testcases (all this should be scrapped and redone with e.g. robolectric) 2016-04-29 21:58:08 +02:00
cpfeiffer 5e02724c4c Make automatic reconnect after connection loss configurable #293
Mi Band: automatically reconnect when the device is back in range

Also: #89
2016-04-28 23:17:13 +02:00
cpfeiffer eca5d40efe More javadoc 2016-04-26 00:02:35 +02:00
Andreas Shimokawa e1551226f6 Reject empty strings in Preferences for numeric inputs 2016-04-25 23:51:58 +02:00
cpfeiffer 47984dba0a javadoc 2016-04-25 23:45:27 +02:00
cpfeiffer e35ce978bd Remove now unused imports + fix one more SharedPreferences usage 2016-04-25 23:43:19 +02:00
cpfeiffer 0704915a88 Move parsing of preference strings to int values to Prefs 2016-04-25 23:39:03 +02:00
cpfeiffer 0c715a2669 Wrap access to SharedPreferences with "Prefs"
(to centralize quirk handling)
2016-04-25 23:18:55 +02:00
cpfeiffer b89eb14be7 allow two digits for number of call notifications (e.g. 60) 2016-04-25 00:13:09 +02:00
cpfeiffer 65bd1581bc Fix receivers, display measured heart rate as a toast again, fixes #292 2016-04-24 18:17:04 +02:00
Andreas Shimokawa 36a34bd17c fix remaining strings saying "App Mananger". Closes #290
(I fixed on transifex where possible, unfortunately some strings vanish there from time to time, I guess it is the case when the english string changes)
2016-04-24 11:37:18 +02:00
Andreas Shimokawa abe1c9070f update German and Korean from transifex, thanks! 2016-04-24 11:32:09 +02:00
Andreas Shimokawa 18fe09bb7c make add icon on FAB white 2016-04-23 23:31:19 +02:00
Andreas Shimokawa 3fefb57fdd Fix colors in alarm activity for dark theme 2016-04-23 23:24:56 +02:00
cpfeiffer d5639a0520 Updated translations from transifex (thanks!) 2016-04-21 23:32:49 +02:00
cpfeiffer c573f989d0 Prepare for 0.9.5 2016-04-21 23:13:06 +02:00
cpfeiffer 0427294227 Dynamically enable/disable logging #288 2016-04-18 00:20:40 +02:00
cpfeiffer a45eacf9b8 WIP: schema update, ... #206 2016-04-17 19:52:51 +02:00
cpfeiffer 97cac282c8 Merge branch 'master' into db-refactoring 2016-04-15 23:05:16 +02:00
cpfeiffer 98d7237ec3 Display a different notification icon, when disconnected
Better icons welcome :-)
2016-04-15 22:57:13 +02:00
Andreas Shimokawa fe310a9df8 Merge branch 'master' of https://github.com/Freeyourgadget/Gadgetbridge 2016-04-15 08:59:58 +02:00
Andreas Shimokawa 7c21f2872a fix travis build 2016-04-15 08:50:26 +02:00
cpfeiffer e451e8155c Remember the map so that we can look up the name later, as well, closes #275 2016-04-14 23:55:40 +02:00
cpfeiffer a8279faa5b Merge branch 'master' into db-refactoring 2016-04-14 23:28:00 +02:00
cpfeiffer a460049a1b Sort by label and blacklist status, hopefully fast enough #275 2016-04-14 23:23:06 +02:00
Andreas Shimokawa a9e7cdcaa7 use some colors from the theme for charts activity 2016-04-14 17:41:04 +02:00
Andreas Shimokawa a9b75a63b3 simply derive AbstractGBFragmentActivity from GBActivity instead of FragmentActivity
This fixes the Actionbar being invisible
2016-04-14 17:16:43 +02:00
Andreas Shimokawa 46086f0408 allow dark theme in charts activity.
The charts however are still the same
2016-04-14 17:04:49 +02:00
Andreas Shimokawa faa6a9d906 use android:summary="%s" for ListPreferences 2016-04-14 16:44:44 +02:00
Andreas Shimokawa f76a1ba16f allow to switch to dark theme im settings 2016-04-14 16:15:58 +02:00
Andreas Shimokawa 367aced03d also use theme in settings 2016-04-14 15:34:53 +02:00
Andreas Shimokawa 4bcebca744 Work towards dark theme, remove -v21 specific theme definition 2016-04-14 15:21:25 +02:00
Andreas Shimokawa a9b4ea8eda bump buildToolsVersion 2016-04-14 15:17:08 +02:00
Andreas Shimokawa 24cc3725d2 equalize size of connected and disconnected device icons 2016-04-14 11:07:44 +02:00
cpfeiffer b25a47c398 Immediately disable hr reading and activity tracking when leaving the tab #273 2016-04-13 23:36:14 +02:00
cpfeiffer e87a357bed Show separate curves when the time between two measurements is too long #273 2016-04-13 21:38:35 +02:00
cpfeiffer f52126ed36 Update dependencies 2016-04-13 21:21:25 +02:00
cpfeiffer ae5d9089d8 Slight improvement to hr charts #178 2016-04-13 21:21:10 +02:00
cpfeiffer 78bf516897 Disabling sleep measurement for continuous measurement is not necessary
Looks like they don't interfere, after all. #178
2016-04-12 23:25:12 +02:00
cpfeiffer f15a97d994 Initial live heartrate measurement in the live activity tab #178 2016-04-12 23:12:50 +02:00
Andreas Shimokawa 58d90c2a66 remove obsolete Holo theming 2016-04-12 13:04:28 +02:00
cpfeiffer 7ab31514dc Rework charts to completely fix crash in charts activity #277 2016-04-11 23:18:12 +02:00
cpfeiffer f334131119 Updated gradle to 2.12 2016-04-11 19:57:54 +02:00
cpfeiffer 82b4394b40 Ignore generated entities 2016-04-10 21:51:47 +02:00
Lem Dulfo 206b0670a4 Fixed mainClassName error, fix entity generation 2016-04-10 21:46:51 +02:00
cpfeiffer 3fb252f74d Merge branch 'master' into db-refactoring 2016-04-10 21:38:11 +02:00
cpfeiffer 290d695fec Add margin for the shadow of the FAB
Also make the hint color grey, like in the discovery activity
2016-04-10 21:36:32 +02:00
Lem Dulfo 39cba84ab1 Apply gray images on non-connected devices in the list. 2016-04-10 21:11:52 +02:00
Lem Dulfo e5726075a4 Rework device item layout 2016-04-10 21:11:52 +02:00
Lem Dulfo eba1ee6dc6 Device disabled icons 2016-04-10 21:11:52 +02:00
Lem Dulfo 70ed14243f Remove all getActionBar occurences, temporary fix for overlap 2016-04-10 21:11:52 +02:00
Lem Dulfo 83e6e6b85f Add background image for empty ControlCenter 2016-04-10 21:11:41 +02:00
Lem Dulfo 80a21f2ec2 Move Discovery functionality to FAB 2016-04-10 21:11:41 +02:00
Lem Dulfo 5a3004cbce AppCompat and FAB, more Material Design 2016-04-10 21:11:41 +02:00
Lem Dulfo 3ef942b5d3 Do not crash on null BT adapter, allows UI work on emulator. 2016-04-10 21:11:41 +02:00
cpfeiffer 5ea107b439 Merge branch 'master' into db-refactoring 2016-04-09 19:19:42 +02:00
cpfeiffer 57ecba16f3 Update Changelog for 0.9.4 2016-04-09 10:05:27 +02:00
cpfeiffer 802e9a8235 OK, revert to connectGatt(false), connect often does not work with true #249 2016-04-09 09:53:03 +02:00
cpfeiffer 22a5aef7d7 Merge branch 'master' into db-refactoring 2016-04-08 23:06:20 +02:00
cpfeiffer 42dda911e4 Fix crash in charts activity, closes #277 2016-04-08 23:02:50 +02:00
cpfeiffer 059c7d0b15 Hopefully fix travis jdk7 build (permgen space issue) 2016-04-08 22:28:06 +02:00
cpfeiffer 3953e4232d Update gradle build tools to 2.0 2016-04-07 20:53:25 +02:00
cpfeiffer 1e5dbb6a23 OK, just connect(true) is not sufficient #249
(we again get connection problems. Let's try this.)
2016-04-07 20:53:25 +02:00
Daniele Gobbetti a49335fa67 Allow to change stored samples converting only certain old types 2016-04-07 17:52:15 +02:00
Andreas Shimokawa b1a93c430d interrupt thread instead of joining to fix ANR 2016-04-07 00:21:21 +02:00
Andreas Shimokawa 6895c5b776 fix xml changelog 2016-04-06 23:29:59 +02:00
Andreas Shimokawa c7b64b6da7 update Japanese translation from transifex (thanks!) 2016-04-06 23:03:56 +02:00
Andreas Shimokawa 10be21e07b add Pebble stuff to CHANGELOG.md (Mi Band is still missing) 2016-04-06 23:00:18 +02:00
Andreas Shimokawa e91b5a07bd Pebble: change delay between reconects to 1,2,4,8,16,32,64 (max) seconds 2016-04-06 22:55:04 +02:00
Andreas Shimokawa 4055cc1173 bump version 2016-04-06 22:44:50 +02:00
Andreas Shimokawa 94cec55a20 Merge pull request #276 from roidelapluie/reconnect
Pebble: Smarter reconnection attempts
2016-04-06 22:41:07 +02:00
danielegobbetti d2af3468f0 Add support for new datalog message added in pebble firmware 3.11
This adds support for storing deep sleep data.
2016-04-06 21:48:16 +02:00
Julien Pivotto e42a041448 Pebble: Smarter reconnection attempts
Sleep several seconds between reconnection attempts:
One second after first attempt, two after the second, and so on.

refs #89
2016-04-06 12:37:23 +02:00
Daniele Gobbetti 51def0d497 Add light intensity to the known steps datalog message.
Add support for record version 6 introduced with firmware 3.11.
There are more data in each record now, but we still do not know what they mean.
Close #270
2016-04-04 23:33:17 +02:00
Daniele Gobbetti 34600e085e Fix wrong assignment, needed to properly deal with datalog messages longer than 255 2016-04-04 23:13:57 +02:00
cpfeiffer 403f74e59b Enable heart rate charts #232
(And fix some charting issues)
2016-04-04 23:05:20 +02:00
Andreas Shimokawa a15b327ff1 Refactoring: get rid of ServiceCommand, use new CallSpec and MusicSpec to pass Call and Music info 2016-04-04 20:08:34 +02:00
cpfeiffer 3e3cf462a6 Attempt to re-enable automatic reconnect (autosensing) #249
(now that initializing device works again)
2016-04-03 23:32:15 +02:00
cpfeiffer 59c3970008 Reuse characteristic objects #234 2016-04-03 23:01:58 +02:00
cpfeiffer b129844169 Small fixes to PR 273 #232
- dynamically toggle hr sleep support when preference changes
- check hr support dynaically after device info is available to avoid false error message
2016-04-03 22:38:06 +02:00
cpfeiffer 7cda9f1923 Merge branch 'computerlyrik-heartrate-sleep-support' 2016-04-03 21:46:29 +02:00
cpfeiffer 804a85d31f Small refactoring of BtLE actions 2016-04-03 21:46:24 +02:00
cpfeiffer b54fe53cd5 Merge branch 'heartrate-sleep-support' of https://github.com/computerlyrik/Gadgetbridge into computerlyrik-heartrate-sleep-support 2016-04-03 21:02:45 +02:00
Andreas Shimokawa 4389c1cca3 Pebble: wait 4 seconds instead of 2 when notifications/calls trigger reconnection 2016-04-03 18:36:30 +02:00
Andreas Shimokawa 7ddfd35c35 Pebble: auto connect on incoming notification or phone call if connection was lost unxpectedly before 2016-04-03 18:30:20 +02:00
cpfeiffer a4919789ca Add some progress to firmware updating #271 #234
Also: remove the low latency mode for firmware update,
because my Mi1S simply disconnects then.

Still missing in the view: device disconnects
2016-04-03 00:50:45 +02:00
cpfeiffer 7a224243a3 Try to quit Gadgetbridge by stopping the service 2016-04-03 00:49:54 +02:00
cpfeiffer 2d10c11005 Log the length of the bytes written 2016-04-02 22:35:37 +02:00
cpfeiffer 0e49535966 Fix progress during fw update #234 2016-04-02 22:24:33 +02:00
Christian Fischer f2de21a664 test if heartrate is supported before writing preferences 2016-04-02 16:11:51 +02:00
Christian Fischer 20aa7d9ad9 add preference to set hartrate sleep detection 2016-04-02 16:09:30 +02:00
Christian Fischer 72258c178c fix in string represantation conversion 2016-04-02 16:08:36 +02:00
cpfeiffer ea5c6a0848 Log ignored notifications when updateCoordinator is null 2016-03-31 21:57:36 +02:00
cpfeiffer 6f97b8c1e5 Log the date that we receive from the Mi Band 2016-03-31 21:54:09 +02:00
cpfeiffer 66c1b3f178 Relax check for Mi1S device detection #234 2016-03-31 21:39:51 +02:00
cpfeiffer 4631df67ac Some more logging + add svg launcher again (somehow it was not added again) 2016-03-30 22:53:08 +02:00
cpfeiffer 776a743285 Move svg file to another place to fix build 2016-03-30 22:06:03 +02:00
cpfeiffer ffc006c21c Fix ordering problem with firmwareInfoSent state variable #234 2016-03-30 21:56:00 +02:00
cpfeiffer cc7f5406ef Use low latency transfer mode for fw update #234 2016-03-30 21:48:42 +02:00
cpfeiffer 5f72daa43a Add SVG launcher icon, closes #190 THANKS! 2016-03-29 22:59:22 +02:00
cpfeiffer f8c761068e Updated for 0.9.3 2016-03-29 22:45:46 +02:00
cpfeiffer e931cf47d7 Need to pass '0' as parameter to mi band fw metadata info #234 2016-03-29 22:15:14 +02:00
Andreas Shimokawa 834a727a39 update CHANGELOG.md 2016-03-29 00:05:29 +02:00
Andreas Shimokawa b3590fed35 For simplicity hide some internal states from the user
Display connecting->connected instead of connecting->connected->initializing->initialized
2016-03-28 23:56:20 +02:00
Andreas Shimokawa cbc57b4407 Pebble: Fix stupid bug that made Pebble Health unavailable in App Manager (Fixes #269) 2016-03-28 23:46:05 +02:00
cpfeiffer 3513a902ae Work towards using greenDAO #206 2016-03-28 23:18:23 +02:00
cpfeiffer 8815f0d134 Small cleanups and fixlets. 2016-03-28 23:02:22 +02:00
Andreas Shimokawa 6ce63276a3 play around with play states, simplify weired nested switch 2016-03-27 19:50:32 +02:00
Andreas Shimokawa adfef3db42 Prepare code for more music metadata (duration, track count, current track number)
Oh and format code
2016-03-27 17:45:44 +02:00
cpfeiffer bfcfe82f17 Improve pairing activity: #254
- add hint about rebooting phone
- request enabling bluetooth
2016-03-26 20:46:48 +01:00
Andreas Shimokawa 9d29e4db3f bump version, update xml changelog 2016-03-26 10:04:02 +01:00
cpfeiffer a70c31f965 Add commandline for running all firmware tests 2016-03-26 00:10:32 +01:00
cpfeiffer 298b7542a4 Update changelog for 0.9.2 2016-03-26 00:01:32 +01:00
cpfeiffer bff5837930 Sort device infos 2016-03-25 23:54:42 +01:00
cpfeiffer 8165751e57 Refactoring to test the double firmware update procedure #234
(while performing the same, known to be working firmware update for Mi1A)

Result: double firmware update procedure works on Mi1A.

Also updated FirmwareTest. Perform all tests not only in the test itself,
but also at runtime before doing the actual update.

Further:
- fix setting of firmwareInfoSent state variable, which prevented installation
  of the section firmware
- make one string translatable
2016-03-25 23:45:27 +01:00
Andreas Shimokawa a208907ba7 update Japanese from transifex (thanks!) 2016-03-25 23:09:25 +01:00
Andreas Shimokawa 98949f3b54 bump version update xml changelog 2016-03-25 11:29:50 +01:00
cpfeiffer 3714ec82da Extracted String "HR: ", (= Heart Rate Firmware Version) 2016-03-24 22:23:16 +01:00
cpfeiffer ce382198d1 updated for 0.9.1 2016-03-24 22:22:58 +01:00
cpfeiffer 89eddb13b0 Fixed connection issues by reading the date from the band #249 2016-03-24 22:10:23 +01:00
cpfeiffer e5b0afb916 Enable low latency during activity sync 2016-03-24 21:26:51 +01:00
cpfeiffer 0e435d6d94 Fix for device item not clickable when info items are visible 2016-03-23 23:34:42 +01:00
cpfeiffer 11ac01f0e8 Set low latency mode during initialization #249
This appears to fix the initialization getting stuck sometimes, e.g.
after turning on bluetooth and then connecting.

The band incidentally sends 0x8 when it's stuck (won't accept the UUID_PAIR
request).
2016-03-23 22:50:42 +01:00
cpfeiffer 1348bad4d3 Improved log output 2016-03-23 22:17:01 +01:00
cpfeiffer 9d9ef8a6f8 Some cleanup 2016-03-23 22:06:48 +01:00
cpfeiffer 71461642f7 Fix enabling "Write Log File" option (closes #261) 2016-03-22 23:07:11 +01:00
Andreas Shimokawa df3a06ac9b Reformat code, make getter of @NonNull members also @NonNull 2016-03-22 21:55:15 +01:00
cpfeiffer b0ec74696d Give better feedback when a firmware cannot be installed #234 2016-03-22 16:12:27 +01:00
Andreas Shimokawa 767f359319 Launch Android's Bluetooth settings instead of our activiy on Android >= 6.0
BLE scanning does not work on Android 6.0 and for the Pebble it does not add any value anyway.
2016-03-22 13:43:01 +01:00
Andreas Shimokawa 5eb2d04513 update README.md 2016-03-22 13:21:24 +01:00
cpfeiffer b419c93254 Disable the test for travis again 2016-03-21 23:44:58 +01:00
cpfeiffer 424d9cd142 More work on firmware detection, recognition and validation #234
Should be as robust as possible now.
2016-03-21 23:42:34 +01:00
danielegobbetti 1933e2bf10 Localize the title of the configuration activity.
Auto open local settings (e.g. clay)
2016-03-21 21:19:32 +01:00
Andreas Shimokawa 1aadcb958b update changelog 2016-03-21 20:20:08 +01:00
cpfeiffer 275839a7f4 last arg of copyOfRange() is index, not length! 2016-03-21 04:18:45 +01:00
cpfeiffer f7b71c1f96 Add logging to firmware detection #234 2016-03-21 04:17:12 +01:00
Andreas Shimokawa 76fc7a2aec always save last device address when connecting, fixes #258 2016-03-20 19:48:54 +01:00
Andreas Shimokawa f046e66bf1 update Italian translation (thanks @danielegobbetti) 2016-03-20 18:45:04 +01:00
danielegobbetti 4a3547228e Update changelog md file and bring xml file on par with it. 2016-03-20 18:15:45 +01:00
Andreas Shimokawa dbeded8d04 In Control Center, do not show alarm configuration in context menu if device does not support it 2016-03-20 17:53:55 +01:00
Andreas Shimokawa c5a7ca4b5b properly re-sync all translation files with transifex
(this only reorders stuff since I had to repush broken tranlations to transifex again)
2016-03-20 15:38:05 +01:00
Andreas Shimokawa 4fe9489909 update German translation 2016-03-20 15:34:07 +01:00
Andreas Shimokawa b5f71febdc bump vestion to 0.9.0, update CHANGELOG.md 2016-03-20 15:16:06 +01:00
Andreas Shimokawa 4be1926459 reformat code though Android Studio 2016-03-20 15:00:05 +01:00
cpfeiffer b3410dcebe Improved testcase #234 2016-03-20 12:18:43 +01:00
cpfeiffer e59c012553 Disable FirmwareTest for travis 2016-03-20 01:16:20 +01:00
cpfeiffer 4f956000c5 Enhanced support for firmware detection, recognition and upgrade #234
Also supports double firmware upgrade for Mi1S.
- so far, only hr firmware upgrade is tested for 1S
- adds junit testcases for firmware recognition and handling
2016-03-20 01:07:57 +01:00
cpfeiffer 6d8d6d5bc8 Testcases for firmware checking 2016-03-20 01:07:57 +01:00
Andreas Shimokawa c2ae9ec530 Update French translation from transifex (thanks) 2016-03-18 22:33:36 +01:00
Daniele Gobbetti 538961fd2c Add some style, intercept and display toast in case of JS errors 2016-03-18 17:50:24 +01:00
Daniele Gobbetti e69fac9704 Do not show the configure menu item for non configurable watch apps. 2016-03-18 16:47:14 +01:00
Andreas Shimokawa 1603d60144 right align info icon in control center 2016-03-17 15:28:43 +01:00
Andreas Shimokawa 89591fd5fe update ukrankian translation from transifex (thanks) 2016-03-16 22:54:46 +01:00
Andreas Shimokawa 61e3cf4348 update japanese translation from transifex (thanks!) 2016-03-16 22:53:36 +01:00
Andreas Shimokawa 238e394d21 update french translation from transifex (thanks) 2016-03-16 22:38:27 +01:00
Andreas Shimokawa c224a40d0e update spanish translation from transifex (thanks!) 2016-03-16 22:37:14 +01:00
Andreas Shimokawa 5906c02330 Merge remote-tracking branch 'origin/danielegobbetti-playground' 2016-03-16 22:27:13 +01:00
cpfeiffer c5a887192d Remove/revert some temporary test code 2016-03-16 00:27:32 +01:00
cpfeiffer e26e6d7b24 Display HR firmware version
Hide fw,hw,hr versions by default and show them on demand with an info
button.
2016-03-16 00:27:32 +01:00
cpfeiffer 4aaf3dd162 Use hw version to make device names unique, then mac addr 2016-03-16 00:27:32 +01:00
cpfeiffer 91f02ae920 WIP: Lots of work towards double firmware update for Mi 1S #234 2016-03-16 00:27:08 +01:00
danielegobbetti ea855a4cc2 Also open public URLs with an external browser. 2016-03-13 08:31:50 +01:00
cpfeiffer 3f39928df5 Some more cursor-related improvements (closing) 2016-03-08 23:48:31 +01:00
cpfeiffer 10975feb49 s/moveToFirst/moveToNext/ 2016-03-08 23:30:31 +01:00
cpfeiffer 9643fa6062 s/moveToFirst/moveToNext 2016-03-08 23:29:42 +01:00
danielegobbetti d378b4eb7b Intercept clay pebblejs://close url 2016-03-08 21:44:12 +01:00
cpfeiffer 7e8281e8d4 Improve exception handling logic a bit 2016-03-08 21:33:12 +01:00
cpfeiffer 87023ebdb3 Don't retrieve the column index again and again in a long loop
Also: fix weird iteration logic
2016-03-08 21:33:12 +01:00
Daniele Gobbetti 2da50e27c2 call the ready event as soon as the app js file has been loaded. Add a button to debug the different steps. 2016-03-08 17:45:11 +01:00
Andreas Shimokawa a89fea9c7d Pebble: Fix crash when starting pebstyle
Also make code for "push" handlers more generic
2016-03-08 12:02:00 +01:00
Andreas Shimokawa 4362f78028 Pebble: Do not display Health on original Pebbles 2016-03-08 11:49:08 +01:00
Andreas Shimokawa a3ee3c15fc Pebble: copy pebble-app-js.js out of the pbw upon installation not upon reading the .pbw
This eliminates the need to copy the whole file into a byte[], and all file size limts are gone.
2016-03-08 11:41:20 +01:00
Andreas Shimokawa 88982a6174 Merge pull request #235 from Freeyourgadget/feature-configuration
Use external browser for configuring pebble apps
2016-03-08 10:41:46 +01:00
cpfeiffer a96120f91d Clear the chart when there are no samples
(e.g. when switching to another day for which no samples are
available, the chart now becomes empty instead of displaying the
samples from the last day with data.
2016-03-07 23:17:02 +01:00
cpfeiffer 5eb8f57b4c Some more byte -> int conversions 2016-03-07 22:47:34 +01:00
cpfeiffer 5ae680cab5 Don't flush the logfile synchronously 2016-03-07 22:46:35 +01:00
cpfeiffer 25e58eb414 Upgrade mpandroidchart to 2.2.3 2016-03-07 22:46:03 +01:00
cpfeiffer be012eca8d For Mi Band 1A (fw 5.15.7.14) we get 0xa for auth success #180 2016-03-07 21:43:45 +01:00
cpfeiffer 50dd7f5eba Better check for heartrate support on non-heartrate devices 2016-03-07 21:36:31 +01:00
Andreas Shimokawa f1ba50b62a update xml changelog 2016-03-07 01:15:23 +01:00
cpfeiffer 2d9673afe7 Updated 2016-03-07 01:10:58 +01:00
cpfeiffer dda6cb34e1 Fix logfile rotation (bug in logback-android) 2016-03-07 01:09:43 +01:00
Andreas Shimokawa 2902e60d51 prepare 0.8.2 2016-03-07 00:55:17 +01:00
cpfeiffer 619ea04a63 Fix database creation and updates #246
The creation script *must* always do the full creation so that fresh installs
get the correct database (no upgrade scripts will run for them)
2016-03-07 00:39:47 +01:00
danielegobbetti 459f6baf08 Fix missing column in the creation script (upgrades were fine, but new installation weren't).
Reindent changelog file.
2016-03-06 17:28:21 +01:00
Andreas Shimokawa fa6b572172 Merge branch 'feature-configuration' of https://github.com/Freeyourgadget/Gadgetbridge into feature-configuration 2016-03-05 21:26:03 +01:00
cpfeiffer 97faf61c5a Log db upgrade/downgrade requests 2016-03-04 23:37:42 +01:00
cpfeiffer dc162a9ac8 Only add column if it doesn't exist yet
Column can exist if there down- and upgrades
2016-03-04 23:25:11 +01:00
cpfeiffer 3b3458e196 Show the heart rate measurement tooltip a little longer 2016-03-04 23:08:13 +01:00
Daniele Gobbetti 6d4b98719a Implement some further JS methods to make additional watchapps happy 2016-03-04 17:44:42 +01:00
Daniele Gobbetti 3920b3f977 Do not override the configured settings with our old stored values (but keep them around) 2016-03-04 17:43:43 +01:00
Andreas Shimokawa f616e4f571 Pebble: skip .js file if too large instead of breaking installation
(Hotfix)
2016-03-03 17:46:58 +01:00
Andreas Shimokawa aba21d3ab7 switch to gradle plugin 1.5.0 in an attempt to fix travis 2016-03-03 17:19:29 +01:00
Andreas Shimokawa a96d042dce try to make mister travis happy 2016-03-03 16:07:59 +01:00
Andreas Shimokawa 3786e0b7f2 fix typo 2016-03-03 16:04:17 +01:00
Andreas Shimokawa 1e44bb03fb Pebble: convert Boolean to String for app configuration 2016-03-03 16:02:30 +01:00
Andreas Shimokawa bd7b34985b reformat code and optimize imports 2016-03-03 15:47:00 +01:00
Andreas Shimokawa 864e0953d9 only allow starting AppManager after device is initalized (else platform cant be determined) 2016-03-03 14:29:46 +01:00
Andreas Shimokawa 902ff39c0b start app when about to be configured 2016-03-03 14:25:44 +01:00
Andreas Shimokawa 2a7f9226a0 Pebble: send configuration to watch
TODO: handle booleans
2016-03-03 14:23:17 +01:00
Andreas Shimokawa fa924ff9d8 Pebble: fix crash when navigating back from configuration activity 2016-03-03 13:05:51 +01:00
Andreas Shimokawa 860ded1022 refromat code 2016-03-03 11:54:07 +01:00
Andreas Shimokawa 63d938559e pass GBDevice down to ExternalPebbleJSActivity to determine the platform version (aplite,basalt,chalk) 2016-03-03 11:52:30 +01:00
Daniele Gobbetti 089a59168e Initial support for using an external browser for configuring pebble apps. This allows existing configuration pages to work without having internet access ourselves.
This is a better approach as initially thought in #191.

What is missing is outlined in the (several) TODOs.
2016-03-02 21:07:27 +01:00
Andreas Shimokawa 652c5575b3 user ckChangeLog to display the Changelog
There were other fancier libraries, but this one was so simple to integrate, that I could not resist ;)
2016-03-02 00:37:43 +01:00
Andreas Shimokawa 5eb525ee44 Merge pull request #239 from 0nse/sleepAlarmWidget
Add widget to quickly set an alarm according to the user's preferred sleep length (in hours)
2016-03-02 00:36:13 +01:00
0nse ba35679690 / Pad the alarm time with zeroes when toasting. 2016-03-02 00:15:31 +01:00
0nse 7651c080c2 / Only execute setAlarmViaAlarmManager when we are running Lollipop+. 2016-03-02 00:05:58 +01:00
0nse 6e7abecb17 / Use GB.toast instead of Android Toast. 2016-03-02 00:01:41 +01:00
Andreas Shimokawa 7756968a8f add a note about supported devices 2016-03-01 12:26:56 +01:00
cpfeiffer 2cdeecb39c 0.8.1 2016-02-29 23:24:35 +01:00
Andreas Shimokawa fc464d112d change some more short and byte to int regarding Morpheuz 2016-02-29 23:13:07 +01:00
Andreas Shimokawa 9adae3b538 bump version, update CHANGELOG.md 2016-02-29 23:06:30 +01:00
cpfeiffer 9dd0a8bf2b Small rewording 2016-02-29 22:13:31 +01:00
cpfeiffer cbe73f71a1 Fix some typos (thanks!) #178 2016-02-29 22:08:34 +01:00
cpfeiffer 1b9f297ebe Updated for Mi1S 2016-02-29 22:04:40 +01:00
0nse 3babedf936 + Add Widget which allows quickly creating an alarm depending on the user's preferred sleep length (in hours). 2016-02-29 21:36:39 +01:00
Andreas Shimokawa ed85fd5011 convert byte and short values related to activity tracking to int
This avoids a lot of problems because java
- does not know unsigned values
- jvm and dalvic do not internally support byte and short
- sqlite does not know them either
2016-02-29 20:54:39 +01:00
cpfeiffer c4dc972804 Fix pmd rules after update gradle to 2.11 (and accordingly, pmd) 2016-02-28 15:14:43 +01:00
cpfeiffer badf384619 Update to latest gradle 2.11 2016-02-28 14:58:17 +01:00
cpfeiffer 1c9be79c67 Add onHeartRateTest() method 2016-02-28 14:52:17 +01:00
cpfeiffer ddde25e5df Initial firmware update support for Mi1S #234 2016-02-28 02:46:48 +01:00
cpfeiffer d7822d07a6 Show the measured heart rate as a Toast, for now #178 2016-02-27 23:27:15 +01:00
cpfeiffer 540e008548 Rename to Heart Rate Test 2016-02-27 23:24:45 +01:00
cpfeiffer 4898dab652 Move "Reboot" button to the very bottom to prevent accidents 2016-02-27 23:24:03 +01:00
cpfeiffer 3ff31cd73b Disable heartrate in charts fow now, fix notification for manual hr 2016-02-27 23:18:44 +01:00
cpfeiffer d6dfc3b6ec Fix some ClassCastExceptions in charts since switching to CombinedChart 2016-02-27 15:30:46 +01:00
Andreas Shimokawa c449181083 Pebble: store appKeys in .json also.
Rumour says someone needs it soon...
2016-02-27 11:40:37 +01:00
cpfeiffer ac8d7bee5f Only do heart rate stuff when supported #178 2016-02-27 11:23:55 +01:00
cpfeiffer de6f898fef Some more fixes regarding heart rate measurement #178 2016-02-27 11:18:16 +01:00
Kasha 9e636d66f6 Initial heart rate support by KashaMalaga #178
(removed unrelated Android M fixes and squashed commits)
2016-02-27 00:05:06 +01:00
cpfeiffer 0ef738067d Some work in progress for heart rate graphs #178
Currently we get the heart rate when synchronizing activity data
(i.e. not live) and we write it to the activity database so that we
can show a nice graph. The value is currently always 0 though,
because we can't enable recording hr, yet.
2016-02-26 23:45:17 +01:00
Daniele Gobbetti df741e9571 Install app on watch directly instead of telling the user to do so. 2016-02-26 15:29:26 +01:00
cpfeiffer a10c6f3b9f Some initial heartrate support #205
(not visible to user yet)
2016-02-26 00:30:57 +01:00
cpfeiffer 0b568df8de Extra byte indeed appears to be heartrate value #205 2016-02-26 00:04:33 +01:00
cpfeiffer 095ef56c14 Initial support for activity data sync with Mi 1S #205
Looks like the activity type is somehow wrong though, or I'm sleeping
all day ;-)
2016-02-25 23:52:34 +01:00
cpfeiffer defa97b882 Log the toast message immediately, not delayed in the main thread
(this helps understanding logs)
2016-02-24 23:53:30 +01:00
Andreas Shimokawa 8de836efb8 Version 0.8.0 2016-02-21 22:09:02 +01:00
cpfeiffer fee04a05ae Updated for Mi Band connection fixes 2016-02-21 21:23:22 +01:00
danielegobbetti b5a726b777 Change layout of the alarms activity, fixes #216. 2016-02-21 17:21:04 +01:00
danielegobbetti 6eb35b955e Prevent race condition on android 6 (?) at the cost of losing the gender data (we cannot display a toast at this point unfortunately). 2016-02-21 16:46:48 +01:00
Andreas Shimokawa db6f26fcd5 bump version, update CHANGELOG.md and README.md 2016-02-21 15:46:53 +01:00
Andreas Shimokawa 1a96bd31e5 Request permissions at runtime on Android 6. Closes #219
TODO: Tell the user why we request that and if he really needs it (if he does not have both a Mi Band and a Pebble she does not need all)
2016-02-21 15:29:18 +01:00
danielegobbetti b858e50804 Use strings to store activity shared preferences.
System has trouble with accessing integer in the preferences, so let's not use them.
2016-02-21 13:04:32 +01:00
Andreas Shimokawa c436c4c055 Pebble: Fix wrong(previous) contact being displayed on the pebble. Fixes #228 2016-02-20 22:20:02 +01:00
Andreas Shimokawa 7626667a0a try to blindly fix user preferences screen 2016-02-19 23:48:08 +01:00
cpfeiffer 109146c8c1 Attempt at fixing a (re-) connection issue
Sometimes reconnection lead only to "Connected" state, but not "Initialized".
This probably happened when the device got disconnected earlier and then was
automatically reconnected. The reconnection closed the previous connection,
which caused the dispatch-thread to wake up and think the connection is
actually establish. Then, when the first action is invoked, it would fail
with an NPE because mBluetoothGatt passed to the action is actually null.
2016-02-18 23:44:01 +01:00
Andreas Shimokawa 70ae5a2a3a Pebble: Allow to select the preferred activity tracker via settings activity (Health, Misfit, Morpheuz) 2016-02-18 20:41:22 +01:00
Daniele Gobbetti cc42583885 add missing newline 2016-02-17 15:19:05 +01:00
cpfeiffer c86365ee2e Some more Mi Band pairing improvements #180
- listen to notifications early -- the band then actually tells us that
  authentication is required
- check for this after sending user info
- add authentication states to GBDevice
- workaround for event problems in pairing activity (delivered although
  already unregistered)
- BtLEQueue now deals with gatt events coming *before* connectGatt()
  actually returned (namely the connection event)
2016-02-13 00:15:16 +01:00
Daniele Gobbetti 8294921de7 Do not ack the sleep data until we can actually store them
Added helper method to fetch the latest timestamp stored in the DB, needed for the aforementioned feature.
Update changelog

This closes #188 \o/
2016-02-11 19:14:40 +01:00
Andreas Shimokawa 7436778700 Pebble: fix for recent morpheuz versions (maybe breaks old versions) 2016-02-11 12:49:01 +01:00
Andreas Shimokawa 823cb12035 Merge pull request #225 from roidelapluie/travis-jdk7-jdk8
Test against JDK 7 and JDK 8
2016-02-09 19:39:44 +01:00
Daniele Gobbetti 20c4e49fe1 Refactoring of the Pebble Health steps data receiver.
Added logic to deal with pebble health sleep data.
Added database helper to change the type of a range of samples (needed for sleep data).
Fixes to the Pebble Health sample provider.
2016-02-09 17:52:21 +01:00
Andreas Shimokawa d62946df63 Pebble: some minor code cleanups regarding health 2016-02-09 01:31:41 +01:00
Andreas Shimokawa 93db073538 Pebble: try to fix health code, might be broken, cant test 2016-02-09 00:56:16 +01:00
Andreas Shimokawa 12a5b53f00 Pebble: Merge DatalogHandler and DataLog session
Also:
- pass the length of the payload and not of the whole datalog buffer to handleMessage(), simplifying DatalogSessionHealth::handleMessage()
2016-02-09 00:49:42 +01:00
Andreas Shimokawa b01a517813 Pebble: fix hexdump for health datalog 2016-02-08 23:33:05 +01:00
Julien Pivotto 5b539d5252 [travis] Test against JDK 7 and JDK 8
Bug #221 was due to a different behaviour of JDK8 and JDK7. To prevent
this in the future, travis should test with both.
2016-02-08 22:34:15 +01:00
Andreas Shimokawa cdb25f3183 Merge pull request #222 from roidelapluie/fix221
Fix #221 - Cast pair.first as integer
2016-02-08 21:32:05 +01:00
Julien Pivotto dd9864015d Fix #221 - Cast pair.first as integer
This commit fixes the following compilation error:

```
:app:compileDebugJavaWithJavac
/home/bob/dev/Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LimitedQueue.java:26:
error: incomparable types: Object and int
            if (pair.first == id) {
                           ^
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
1 error
:app:compileDebugJavaWithJavac FAILED

FAILURE: Build failed with an exception.
```
2016-02-08 06:32:36 +01:00
Andreas Shimokawa 0c4e606e74 Pebble: rename BLOBDB_HEALTH to BLOBDB_PREFERENCES and encodeSaneDistanceUnit to encodeSetSaneDistanceUnit
Also allow to set insane units in the method
2016-02-07 21:59:14 +01:00
danielegobbetti 10b5c571bb Use Kilometers as distance unit 2016-02-07 16:44:16 +01:00
danielegobbetti 2f8207abf9 Initial support for reading pebble health steps/activity data. 2016-02-07 09:27:51 +01:00
Andreas Shimokawa 299b850a67 its release time 2016-02-06 21:48:33 +01:00
danielegobbetti 03ad7f5a24 Do not enable insights on the watch. Add notice about health activation to changelog. 2016-02-06 21:38:55 +01:00
danielegobbetti ba9e00d2e4 Add strings for activate and deactivate pebble Health 2016-02-06 21:23:21 +01:00
danielegobbetti a451b37ceb Added refactoring of user details to changelog 2016-02-06 19:48:03 +01:00
Andreas Shimokawa 3db88574fa bump version, update CHANGELOG.md (not yet released) 2016-02-06 19:41:21 +01:00
Andreas Shimokawa 59d6553c54 Pebble: fix stupid bug that broke active reconnection 2016-02-06 19:35:49 +01:00
Andreas Shimokawa 85bad9abf5 Pebble: store information about datalog sessions (uuid, item type, length, tag)
... and log them if data comes in from a known id.
Also request open sessions on connect.
And last but not least hex dump data which might be from Health (the tags that I never see on Aplite but always on Basalt)
2016-02-03 23:27:35 +01:00
Andreas Shimokawa 493fcfc853 Pebble: improve datalog output 2016-02-03 20:23:56 +01:00
Daniele Gobbetti baf5eee72f Refactored the User Activity-tracking related preferences.
Created a new device-independent class ActivityUser to hold the data
Moved the constants from the miband constant class to the ActivityUser class
Removed the miband-specific in favor of common-prefixed preferences (with upgrade support for legacy values)
Changed the way the gender is stored to an integer value
Removed the hardcoded default values for user data in favor of static fields of the ActivityUser class
2016-02-02 17:33:24 +01:00
Daniele Gobbetti 94c8633bad Move the generic user info outside the miband preferences activity. They still have to be renamed. 2016-02-02 14:32:19 +01:00
Andreas Shimokawa 703cbd1745 Merge pull request #220 from chrisperelstein/spelling-fix
Fix misspelling in exception notification.
2016-02-02 10:22:30 +01:00
Chris Perelstein 60c7e9f6f6 Fix misspelling in exception notification. 2016-02-01 22:05:49 -05:00
Andreas Shimokawa cc64bcf03c updated Korean and French from transifex (thanks!) 2016-01-31 00:57:16 +01:00
Daniele Gobbetti 5b016e2577 WIP, read the miband user information for the time being. 2016-01-29 17:45:35 +01:00
Daniele Gobbetti 8e7dc18fa8 Merge branch 'master' into feature-weather 2016-01-28 12:00:47 +01:00
Daniele Gobbetti 9f2a7f5448 actual deletetion of the Health app.
It brings the health watchapp back to the pristine state "you need pebble health in order to use this app". The data, however, is not deleted!
2016-01-28 11:59:23 +01:00
Daniele Gobbetti f9d2fddb7a Android studio 2 complains about this comparison otherwise 2016-01-28 11:12:28 +01:00
Daniele Gobbetti e7c93ca1c3 Merge remote-tracking branch 'origin/master' into feature-weather 2016-01-28 11:11:20 +01:00
Andreas Shimokawa 33cf76172b Pebble: add hack to enable and maybe disable Health from the App Manager activity 2016-01-27 23:02:09 +01:00
cpfeiffer b9cb89ab8b Component diagram giving an overview
(created with umbrello)
2016-01-26 23:05:54 +01:00
Andreas Shimokawa 666c53a1e4 Pebble: make encodeBlobDB accept Strings and UUIDS as keys, use it for Heath activation 2016-01-26 10:48:50 +01:00
danielegobbetti 7ba3a874a2 Message to activate Pebble Health, possibly. 2016-01-25 23:15:19 +01:00
Daniele Gobbetti c08d49d28e Merge remote-tracking branch 'origin/master' into feature-weather 2016-01-25 13:12:30 +01:00
Daniele Gobbetti 1ac7e08289 Merge branch 'feature-weather' of https://github.com/Freeyourgadget/Gadgetbridge into feature-weather 2016-01-25 12:44:11 +01:00
Andreas Shimokawa 857e282bdc bump version 2016-01-24 22:48:51 +01:00
Andreas Shimokawa 9af976657b Pebble: Report correct connection state to PebbleKit companion apps (not always connected) 2016-01-24 00:06:44 +01:00
Andreas Shimokawa e6f68f445a Ignore generic notification when from SMSSecure when SMS Notifications are on.
This should improve (not fix) #214
Still, we cannot decrypt SMS, so if you use SMSSecure as the default SMS App
you should disable SMS Notifications which enables generic notifications for
SMSSecure which are already decrypted.
2016-01-22 21:30:50 +01:00
danielegobbetti 3d3643ece3 Add weather to PebStyle as well. 2016-01-22 21:04:01 +01:00
danielegobbetti 11297cd855 Merge remote-tracking branch 'origin/master' into feature-weather 2016-01-22 20:48:26 +01:00
Andreas Shimokawa a72373c17c move PebbleContentProvider.java 2016-01-22 20:38:44 +01:00
Andreas Shimokawa b9c1332442 Testing content provdider 2016-01-22 20:21:18 +01:00
Daniele Gobbetti ea1ce4f43f Merge remote-tracking branch 'origin' into feature-weather 2016-01-22 09:47:15 +01:00
Andreas Shimokawa a6ce10d306 translate some string to German, no idea about the rest. 2016-01-21 21:46:14 +01:00
Andreas Shimokawa cf1c245e58 update CHANGELOG.md, README.md and bump version 2016-01-21 21:40:23 +01:00
Andreas Shimokawa 2b78b0a67f Merge pull request #213 from schlamar/patch-1
fix spelling
2016-01-21 21:30:45 +01:00
Andreas Shimokawa 31724b3ef2 Update Spanish, French, Japanese and Russian from transifex (thanks!!!) 2016-01-21 21:27:31 +01:00
Marc Schlaich 887b478ec6 fix spelling 2016-01-21 19:09:10 +01:00
Andreas Shimokawa 818c31eb2b update CHANGELOG.md 2016-01-11 15:47:09 +01:00
Andreas Shimokawa de4ffe8fb0 allow to setup a common suffix for canned replies (defaults to " (canned reply)") 2016-01-11 15:29:12 +01:00
Andreas Shimokawa 7ba156da62 Try to support MarioTime.
Does not work :/
2016-01-10 20:12:52 +01:00
danielegobbetti 4bb78722b5 Merge branch 'master' of https://github.com/Freeyourgadget/Gadgetbridge into feature-weather 2016-01-09 18:48:39 +01:00
Andreas Shimokawa 0b53f60b0d Pebble: EXPERIMENTAL support for replying to wearable notifications
Tested with Signal, more could work.
2016-01-09 17:54:17 +01:00
Andreas Shimokawa 46bbab7df0 rename IDSenderLookup to LimitedQueue and store Object instead of String 2016-01-09 16:07:22 +01:00
Andreas Shimokawa 803e58743a add WearableExtender with reply action to debug notification
(This is for testing new features)
2016-01-09 15:33:18 +01:00
cpfeiffer ae5417b9cc Avoid NPEs when aborting an erroneous sync #205 2016-01-07 00:33:20 +01:00
cpfeiffer a6d3c50f94 Fix discovery of Mi Band 1S #196
closes #204
2016-01-06 23:38:17 +01:00
cpfeiffer 824382b385 New translation from transifex, thanks! 2016-01-06 21:26:59 +01:00
cpfeiffer 779d8ee930 Add a confirmation dialog when performing a db import #197 2016-01-06 00:24:39 +01:00
cpfeiffer 41dde9a9c2 Programmatically perform a bonding
(i.e. bluetooth-level pairing). Previously the pairing appeared to
happen automagically, but this doesn't work (anymore). So now we first
pair on the bluetooth-level, then application level.
2016-01-06 00:16:41 +01:00
danielegobbetti 3d389f31a3 fix force close when weather hasn't been parsed yet, use the yahoo codes for this watchface, as required 2016-01-05 16:05:12 +01:00
danielegobbetti d7f4769f57 make the conversion methods static 2016-01-05 16:04:32 +01:00
danielegobbetti 092af9f38d fix forecast condition parsing 2016-01-05 16:03:59 +01:00
danielegobbetti c7c723134e Add weather singleton (to store the whole data passed by weather notification).
Add weather info for TimeStylePebble.
Add further fields to the ParcelableWeather class.
Add reverse mapping to ParcelableWeather to get back the original OpenWeatherMap API condition codes.
2016-01-03 18:28:32 +01:00
danielegobbetti 1f1ac8cf37 Merge branch 'master' of https://github.com/Freeyourgadget/Gadgetbridge into feature-weather 2016-01-03 16:36:09 +01:00
danielegobbetti abb7ed0189 Manual mapping between openweathermap and yahoo weather conditions. 2016-01-03 16:33:48 +01:00
Andreas Shimokawa c425fd24ea sort blacklist by package name #195 2016-01-02 17:18:05 +01:00
Andreas Shimokawa e41a9c208b update spanish translation from transifex (thanks!) 2016-01-02 12:26:15 +01:00
Andreas Shimokawa d7f74851e2 Pebble: allow reinstallation of apps in pbw-cache from App Manager (long press menu)
See #93
Also bump version, update CHANGELOG.md
2016-01-02 12:24:23 +01:00
Andreas Shimokawa 50cd5b2629 Pebble: Fix regression which freezes Gadgetbridge when disconnecting via long-press menu 2016-01-02 00:30:11 +01:00
Andreas Shimokawa d358ed81d2 Pebble: Display a failure if we cannot map a notification id back to a Phone number when replying to an SMS 2015-12-31 01:43:00 +01:00
Andreas Shimokawa 890016d652 Pebble: Fix crash when turning off bluetooth when connected on Android 6.0 2015-12-31 01:13:48 +01:00
Andreas Shimokawa 3655c833a9 Add new languages from transifex (Polish and Vietnamese). Thanks!! 2015-12-31 00:57:45 +01:00
Andreas Shimokawa ba446b7b17 update spanish translation from transifex (thanks) 2015-12-31 00:41:22 +01:00
Andreas Shimokawa ae269e51e7 update README.md, bump version 2015-12-31 00:40:22 +01:00
Andreas Shimokawa e533fdbaa6 Pebble: actually display current whether in WeatherNeat 2015-12-29 22:10:38 +01:00
Andreas Shimokawa eb50040320 update CHANGELOG.md 2015-12-29 20:56:30 +01:00
Andreas Shimokawa 790e4d5d80 update LICENSE.artwork 2015-12-29 20:45:37 +01:00
Andreas Shimokawa c962dbbac2 Merge branch 'master' into feature-weather 2015-12-29 20:14:32 +01:00
Andreas Shimokawa a7ea2141d6 Merge branch 'master' into feature-weather 2015-12-29 20:13:31 +01:00
Daniele Gobbetti 1cbe965802 Add analog+digital settings 2015-12-29 10:45:14 +01:00
Daniele Gobbetti f9122bc674 send the calendar events each time the time is sent to miband, until we have a common strategy (e.g. EventHandler). 2015-12-29 10:01:32 +01:00
Daniele Gobbetti 1d9e1d7caf Fix possible race condition when availableSlots == 3
(There are two difficult things...)
2015-12-29 09:18:02 +01:00
Andreas Shimokawa af9ee90383 actually get weather, seems to be the only way 2015-12-28 20:55:59 +01:00
danielegobbetti aa1014f51c Added dummy configuration file. This way we get data from the weather notification app.
Problem is we probably ned to add it as library, in order to unmarshal the intent. (That's why I commented the offending code in the receiver)
2015-12-28 17:46:16 +01:00
Andreas Shimokawa 7a1a6dbb2b WeatherNotification: Try to dissect the parcelable extra
This is useless, since we do not get the extra at all (only weather skins)

So... this is a dead end...
2015-12-28 11:33:22 +01:00
cpfeiffer 9ea2977143 Try hard to display a unique device name in ControlCenter
(Makes it a little easier to deal with muliple "MI" devices, for example)
2015-12-28 00:16:00 +01:00
Andreas Shimokawa e3d0c63676 fix PebStyle color 2015-12-27 21:39:46 +01:00
Andreas Shimokawa 483c435aa5 Commit old weather experiment - might be worth continuing 2015-12-27 19:44:33 +01:00
Andreas Shimokawa 55989c426c fix double SMS on Android 6.0 2015-12-27 19:22:10 +01:00
Andreas Shimokawa 2caef02309 fix typo :/ 2015-12-27 19:16:56 +01:00
Andreas Shimokawa 11e02fbf5f Pebble: add experimental reconnect support for standby-mode on firmware 3.x
- You should also set reconnect attempts to 0 in preferences when using this.
- It also works when you set flight mode on the pebble, then wait for about 5 minutes and turn BT back on
- Pebble FW 2.x support ist completely untested.
2015-12-27 19:11:40 +01:00
Andreas Shimokawa 9f60bf3561 fix compilation 2015-12-23 22:15:50 +01:00
Andreas Shimokawa 15436c59e5 reformat AndroidManifest.xml 2015-12-23 22:01:26 +01:00
Andreas Shimokawa cf5a0f19ed update launcher icon (thanks @halcyonest) 2015-12-23 21:40:49 +01:00
Daniele Gobbetti 3ee418a45b Implement pushing messages upon watchapp start.
This watchface is used as example: https://github.com/ygalanter/PebStyle because it doesn't initiate a connection like others do.

At the moment this is more a proof of concept^W^W^Wdirty hack then anything else.
2015-12-23 14:22:28 +01:00
Daniele Gobbetti 5f189aedbd Add support for some configuration options of TimeStylePebble watchface ( https://github.com/freakified/TimeStylePebble ) 2015-12-23 09:04:01 +01:00
Daniele Gobbetti 26646af974 Put blocked apps at the beginning of the list, for easier removal. 2015-12-22 14:09:14 +01:00
Andreas Shimokawa 0c805809a5 Pebble: allow pbw installation with appinfo.json files up to 64k (was 8k)
Fixes DIN watchface installation
2015-12-21 22:32:27 +01:00
Andreas Shimokawa 87739d94db Pebble: unify FW 2.x and 3.x action decoding 2015-12-20 21:23:39 +01:00
danielegobbetti a71c27d25e Added subtitle to timeline pins 2015-12-20 19:50:48 +01:00
Andreas Shimokawa 96e21dbf21 Pebble: fix "[Action]" being displayed as SMS action when no canned replies are defined 2015-12-20 00:58:14 +01:00
Andreas Shimokawa 35c7ab6dde PEbble: add database parameter to encodeBlobDBClear(), minor cleanups 2015-12-20 00:40:38 +01:00
danielegobbetti 5026cf269f Added command to clear the DB on pebble. 2015-12-19 20:52:44 +01:00
Andreas Shimokawa 4b29d63d4e Pebble: more debugging 2015-12-19 20:30:46 +01:00
danielegobbetti 070f3fa66f Add further getters, converting the data to other units 2015-12-19 20:26:43 +01:00
danielegobbetti 9fb2e1620e Rename misnamed file. 2015-12-19 14:36:28 +01:00
danielegobbetti 9acdefd5c1 Treat sony SMS app as such for notifications. 2015-12-19 14:30:35 +01:00
danielegobbetti 6582ead01c Add pebble health uuid. 2015-12-19 09:43:55 +01:00
Andreas Shimokawa 7eabf1e603 spoof android app version to 3.8.1 (was 3.0.1) 2015-12-18 21:34:52 +01:00
danielegobbetti 89ef950c62 Add the feature to changelog.
Fix max index for alarms on miband.
2015-12-18 20:30:09 +01:00
danielegobbetti 5fb8c7bed8 Use the right column for sorting upcoming events.
Plus add forgotten getQueue
2015-12-18 18:49:29 +01:00
Andreas Shimokawa 47a34bb7bf Pebble: debug output for blobdb responses (get rid of unhandled endpoint -20005 messages) 2015-12-18 12:41:01 +01:00
Andreas Shimokawa c9dcf06529 Pebble: improve debug output
- decode apprunstate and systemmessage endpoint messages
- prevent error messages by not using appmanager endpoint on firmware 3.x (it is gone)
2015-12-17 23:09:52 +01:00
Daniele Gobbetti 036e92ee64 Implement sending the upcoming events to the miband.
The reserved alarm slots are used for storing the upcoming events.

The method is not yet called anywhere.
2015-12-17 18:02:21 +01:00
Daniele Gobbetti 78cd11ad93 Hide the last N reserved alarm slots from the Alarm activity.
The original values remain stored in the preferences, they are just not available to the user anymore, further they are not sent to the device.
2015-12-17 17:02:00 +01:00
Daniele Gobbetti 0dda5c214b Add preference for reserved alarm slots (for calendar event notification)
Just this, no logic to support the choice yet.
2015-12-17 16:11:35 +01:00
Andreas Shimokawa 7b12a3b50c forgot this 2015-12-17 00:30:55 +01:00
Andreas Shimokawa f387f7c96b increase canned replies from 8 to 16
NOTE:

Total allowed bytes for all replies = 512 - (reply count - 1)

TODO:
- check with Firmware 2.9.1
- remove last reply that exceeds the 512 bytes limit completly (else it will be partly truncated)
2015-12-17 00:05:42 +01:00
Andreas Shimokawa 87674db5f9 update translation from transifex 2015-12-16 23:21:53 +01:00
Andreas Shimokawa c6e67a9059 update icons again (thanks xphnx) 2015-12-16 23:08:27 +01:00
Daniele Gobbetti 19afe23703 Read events from android calendars, one week in the future.
- Needs new permission.
- Stores the details needed for the pebble timeline and the miband
- Device support to be done
2015-12-16 14:45:01 +01:00
Andreas Shimokawa ddd196fab4 Merge pull request #187 from DaniPhii/patch-1
Updated Spanish translation
2015-12-15 14:54:08 +01:00
Ⲇⲁⲛⲓ Φi 53f8221f98 Updated Spanish translation
Added a line translating "Canned replies".
2015-12-15 13:43:16 +01:00
Andreas Shimokawa dfa85745e8 Send out SMS when replying with a canned response
Closes #121
2015-12-15 00:26:06 +01:00
Andreas Shimokawa 3961e32c2b update korean translation from transifex (thanks!) 2015-12-14 23:47:50 +01:00
Andreas Shimokawa de5f30ae97 WIP: Work towards SMS replies / canned replies, round 3
- put random id/phone number pair into limited lookup list (last 16 sms messages) when sms arrives
- lookup the phone number when replying from the a device

THIS STILL DOES NOT DO ANYTHING USEFUL
2015-12-14 23:31:31 +01:00
Andreas Shimokawa 14f8929439 Pebble: fix decoding strings in appmessages from the pebble
This fixes sending SMS from "Dialer for Pebble"
2015-12-14 00:05:14 +01:00
Andreas Shimokawa e5cf22bda6 WIP: Work towards SMS replies / canned replies, round 2
- parse the reply string in PebbleProtocol
- put replies into GBDeviceEvents
- display a toast in AbstractDeviceSupport, containing the reply

THIS STILL DOES NOT DO ANYTHING USEFUL
2015-12-13 22:43:53 +01:00
Andreas Shimokawa 53fb63781e WIP: Work towards SMS replies / canned replies
- Implement the PebbleProtocol side (2.x and 3.x)
- Add Preferences for canned replies

This can be tested by enabling untested features in Pebble Settings
It lets you see and select the replies set up in "Canned Repies" on the Pebble
You will get a "NOT IMPLENTED" message on your Pebble.

THIS DOES NOT ACTUALLY DO ANYTHING USEFUL YET.
2015-12-13 12:03:57 +01:00
cpfeiffer f258e62633 Refactoring: centralize GBDevice creation
- created and provided by DeviceHelper
- passed from UI to service
- without UI, service uses DeviceHelper directly

=> Cleaner and less duplicated code
2015-12-13 00:43:07 +01:00
Andreas Shimokawa 7cf1e0e004 Add system app icon and make use of it (thanks xphnx!) 2015-12-12 11:59:52 +01:00
Andreas Shimokawa b1954eec3e make use of some new icons 2015-12-11 21:38:43 +01:00
Andreas Shimokawa c9d1b9dd4a add new icons (thanks xphnx!) 2015-12-11 21:24:00 +01:00
Andreas Shimokawa 4528aaf22f Pebble: Allow installing apps compiled with SDK 2.x also on the balast platform (pebble time, pebble time steel) 2015-12-11 21:11:55 +01:00
cpfeiffer 854a7ee1ac Avoid NPE when DeviceInfo is null
(when a notification comes in while we're connected, but not initialized yet)
2015-12-09 17:55:46 +01:00
cpfeiffer 794ae6d800 Well, we better return something usefull instead of just false :-]
Please try again #178
2015-12-09 17:54:54 +01:00
cpfeiffer 6d7428ad29 Require initialized state for some more actions 2015-12-09 00:03:39 +01:00
cpfeiffer 66ed672ad6 Updated translations from transifex (thanks!) 2015-12-08 23:50:36 +01:00
cpfeiffer a1fe16bbcb Updated changelog 2015-12-08 23:49:41 +01:00
cpfeiffer e642971b4c Support for deleting/emptying the activity database 2015-12-08 23:42:58 +01:00
cpfeiffer fe60d6aaf0 Avoid popping up the keyboard when entering the debug activity 2015-12-08 23:41:45 +01:00
cpfeiffer 0d63e5b770 User Info is probably the same for MI 1A and 1S #178
Can you please test whether this fixes the connection problems?
2015-12-08 21:48:56 +01:00
cpfeiffer 159c187e5e On Quit, remove all pending notifications
At least the notifications from #141 don't stay there forever, then.
2015-12-08 21:48:56 +01:00
Andreas Shimokawa 8b87e97f51 fix crash in FileUTils.copyURItoFile() if source and destination are the same file.
This allows to install pbw files from the pbw-cache manually though a file manager.
2015-12-08 18:59:37 +01:00
Andreas Shimokawa 804621aa14 bump version to 0.6.9 2015-12-08 18:35:45 +01:00
Andreas Shimokawa f59382e3c8 Pebble: Show correct icon for activity tracker and watchfaces in app installer (language and fw icons still missing) 2015-12-08 18:32:17 +01:00
Andreas Shimokawa 18726eca33 Pebble: Correct setting the timezone on firmware 3.x (pebble expects the "ID" eg. Europe/Berlin) 2015-12-08 14:59:24 +01:00
Andreas Shimokawa 62c196eb1d Pebble: determine pbw type early for correct display in pbw-cache on firmware 3.x 2015-12-08 13:23:07 +01:00
Andreas Shimokawa 3123f30e5a update README.md 2015-12-08 12:11:26 +01:00
Andreas Shimokawa 3ac00a004f Pebble: Support installing .pbl (language files) on firmware 3.x 2015-12-08 12:07:14 +01:00
cpfeiffer 7f8ba83aab This might fix the missing progress bar #155
setVisibility(Public) is only available since Lollipop.
2015-12-07 23:09:19 +01:00
Andreas Shimokawa 1c3e0b628b Pebble: store app details in pbw-cache and display them in app manager on firmware 3.x
Improves #93
2015-12-07 18:16:23 +01:00
cpfeiffer 44667a60d1 Hint about version increments 2015-12-07 18:10:00 +01:00
Andreas Shimokawa f20e11d517 Pebble: Increase maximum notification body length from 255 to 512 bytes on firmware 3.x 2015-12-07 12:25:34 +01:00
Andreas Shimokawa 803a3bea90 increment versionCode 2015-12-07 07:42:07 +01:00
cpfeiffer 265dcd25eb Release 0.6.28, update translations from transifex (thanks!) 2015-12-07 01:32:39 +01:00
cpfeiffer accb055307 Use parentActivityName instead of android.support.PARENT_ACTIVITY
(the latter doesn't work here)
2015-12-07 01:26:58 +01:00
cpfeiffer 134eeacaea Whitelisted 5.15.7.14 2015-12-07 01:22:27 +01:00
cpfeiffer 365ce61cb6 Support for firmware upgrade/downgrade on Mi 1A.
I hope I didn't break firmware upgrades on some Mi 1 models
other than mine (my hardware revision is 2).

Upgrades for Mi 1S are currently disabled, we need some brave
souls who can help us test this.

Closes #173
Also see: #169
2015-12-07 01:14:06 +01:00
cpfeiffer 6b053c4240 Make method protected, for future use 2015-12-07 01:13:47 +01:00
cpfeiffer 5a479c9175 fix typo 2015-12-07 01:13:47 +01:00
cpfeiffer aa60ce4b56 Disable some notifications also for update-firmware operation
(introduce a common superclass to do that)
2015-12-07 01:13:47 +01:00
cpfeiffer 579546c9f8 Display a toast when bonded 2015-12-07 01:13:47 +01:00
cpfeiffer b056e1b2a0 tiny log addition 2015-12-07 01:13:47 +01:00
cpfeiffer 8cd6bf09a4 Fix NPE when an error occurs during activity synchronization
(activityDataStruct is null after handleActivityFetchFinish())

Also remove superfluous log statement -- toasts are logged as well.
2015-12-07 01:13:47 +01:00
Andreas Shimokawa 31599f2776 Mention "Stand-By-Mode" in README, replace "Pebble Time" with "Firmware 3.x" where appropriate 2015-12-06 21:28:43 +01:00
Andreas Shimokawa b05cfc6aac Pebble: fix crash on firmware 3.x when pebble requests a pbw that is not in Gadgetbridge's cache 2015-12-06 17:56:46 +01:00
Andreas Shimokawa 79f92b8495 Fix installing pbw files from different URIs than local files on Firmware 3.x. Fixes #183 2015-12-06 17:22:07 +01:00
Andreas Shimokawa 9ebb320e10 fix installing pbz/pbw files from content provides (eg. download manager)
This still does not fix pbw isntallation problems with FW 3.x since the content does not get copied to the pbw cache yet when content providers are involved
2015-12-06 16:48:43 +01:00
Andreas Shimokawa 05a8436f7c Treat notifications from Signal as chat messages 2015-12-06 16:10:46 +01:00
Andreas Shimokawa 39e09946cd fix missing assignment *cough* 2015-12-06 15:39:34 +01:00
Andreas Shimokawa 112dfa184a Catch securtiy exception if contacts can't be read on Android 6.0. Closes #182 2015-12-06 15:32:48 +01:00
Andreas Shimokawa a8db606240 Link to wiki for pebble firmware updates 2015-12-06 12:48:43 +01:00
Andreas Shimokawa bf61147224 update README
- remove note about firmware updates on 3.x problably not working (as they seem to work fine)
- remove note about pre Android 4.4, it won't be done.
2015-12-06 12:33:53 +01:00
Andreas Shimokawa 0cf6e61ca6 update CHANGELOG.md and bump version 2015-12-05 16:25:15 +01:00
Andreas Shimokawa bc108ba095 Pebble: support installing 3.x apps on OG Pebble 2015-12-05 16:06:42 +01:00
Andreas Shimokawa f3a33cb620 update CHANGELOG.md 2015-12-01 11:58:36 +01:00
Andreas Shimokawa aca0149b45 use DeviceHelper in DeviceSupportFactory to determine supported device from bt addresses
This fixes a bug when Pebble was detected as Mi when unpaired.
Since we were not able to read the device name, it was considered MI by duplicated and
faulty code. Fixes #177.
2015-12-01 11:39:34 +01:00
Andreas Shimokawa 729555b045 update translations from transifex (thanks) 2015-12-01 09:47:13 +01:00
cpfeiffer 40d7f3b19f Improved log output for progress actions 2015-11-30 23:42:59 +01:00
cpfeiffer 4b42a9a4f6 Improved log output for abort actions 2015-11-30 23:42:59 +01:00
cpfeiffer 8585572197 Improved log output for plain actions 2015-11-30 23:42:59 +01:00
Andreas Shimokawa 17ba49374c Fix copy&paste error in K9Receiver.java, update CHANGELOG.md 2015-11-27 23:45:27 +01:00
Andreas Shimokawa c768107db8 Catch permission errors in K-9 Mail receiver. Closes #175. 2015-11-27 22:42:47 +01:00
cpfeiffer bd0716ba58 Some more lint warnings fixed 2015-11-23 23:04:46 +01:00
cpfeiffer 95dc67c98d Back to sdk level 19 (#173) 2015-11-23 22:49:11 +01:00
cpfeiffer 81c2f657bd Some lint fixes 2015-11-23 22:46:12 +01:00
cpfeiffer a7a061e298 Update gradle built tools to 1.5 2015-11-23 22:11:16 +01:00
cpfeiffer 4616dcc965 Handle case where notification.extras is not available #174 2015-11-23 22:09:47 +01:00
cpfeiffer 394a0905dc Avoid potential, but very unlikely NPE 2015-11-23 21:59:13 +01:00
cpfeiffer 4622b384f2 Add comment regarding commented out internet permission
(Pebble Emulator)
2015-11-23 21:42:35 +01:00
cpfeiffer c1d9777047 Remove superfluous minSDKVersion in AndroidManifest.xml 2015-11-23 21:41:06 +01:00
cpfeiffer 52047b615c Drop minSDKVersion to 18 #174
Please test :-)
2015-11-23 21:39:08 +01:00
cpfeiffer a53f1c21eb Simplification 2015-11-23 21:39:08 +01:00
Carsten Pfeiffer be644befb4 Merge pull request #171 from nico202/master
Fixed italian typo
2015-11-23 00:39:30 +01:00
Nicolò Balzarotti 99c97ccda7 Fixed italian typo 2015-11-20 23:43:24 +01:00
cpfeiffer 34e08f6de8 Avoid potential NPE. Check for null first. 2015-11-18 23:21:56 +01:00
cpfeiffer 1e6db708d2 Fix NPE.
Now that is a really crappy API in Android that returns an array with null values.

closes #167
2015-11-18 23:18:01 +01:00
Andreas Shimokawa ea98e207d9 bump version, update CHANGELOG.md 2015-11-15 10:32:43 +01:00
Andreas Shimokawa 1734e58f70 Pebble: Initial try to interpret sleep 2015-11-15 10:30:28 +01:00
cpfeiffer ebbb71ae9d Update MPAndroidChart to 2.1.6 2015-11-13 23:57:04 +01:00
cpfeiffer f349846f4a Another small change to BTLE device connection #156 2015-11-13 23:53:48 +01:00
cpfeiffer 5864189b91 Update changelog 2015-11-09 23:56:46 +01:00
cpfeiffer 2e267a4c2b Temporary workaround for totally wrong sleep stats
The reason being that we filter samples by activity kind
and then calculate the total sleep time using a delta between two
consecutive sample timestamps. But due to filtering of samples, not
all samples are consecutive. Instead of we have "holes" and add those
to your sleep time.

The data in the db is correct though (it always is), it's just the
display in the app that is wrong.
2015-11-09 23:56:46 +01:00
cpfeiffer d9722c6db2 Translate strings, remove unused variable 2015-11-09 23:56:46 +01:00
Carsten Pfeiffer 193d74879a Merge pull request #161 from sarg/master
Correct buildTools version in travis.yml
2015-11-09 22:32:07 +01:00
Sergey Trofimov fc2cc3adb4 attempt travis fix
use same suggestion as per
https://github.com/travis-ci/travis-ci/issues/5036
2015-11-09 11:13:08 +03:00
Sergey Trofimov b70fbeccc9 Correct buildTools version in travis.yml 2015-11-06 17:39:07 +03:00
cpfeiffer 753286a341 Update translations from transifex (thanks!) 2015-11-03 20:44:57 +01:00
cpfeiffer da5acf748a Update changelog 2015-11-03 20:44:36 +01:00
cpfeiffer 58cfd0fef9 For a start, connect with "false" #156
My connection problems went away with this. Let's see how it works out.
2015-11-03 20:23:48 +01:00
cpfeiffer d8960c4e16 Update some dependencies 2015-11-01 23:32:43 +01:00
cpfeiffer c27459b6cc It's recommended to cancel discovery before connecting 2015-11-01 23:32:25 +01:00
cpfeiffer 952a383856 Use batched database commits for Mi Band activity samples 2015-11-01 21:41:36 +01:00
cpfeiffer d4f070f0aa Simplify external dir handling again -- prefer the primary dir
The primary external dir is often a user-partition on the internal
storage medium. This one is safe in multi-user environments.

The "removable storage" (sdcard) on the other hand can be read by
everyone. If the former is not available or not writable, use the
latter.

closes #153
2015-11-01 20:49:50 +01:00
cpfeiffer 8920f5e95b Test fixing problem with non-writable getExternalFilesDir() #153 2015-10-29 00:46:52 +01:00
cpfeiffer 1f599c660f Improved file and error handling 2015-10-28 23:54:08 +01:00
cpfeiffer 694b3d897f Some Javadoc 2015-10-26 23:32:03 +01:00
cpfeiffer a7ebed94f7 Update to logback-android 1.1.1-4 2015-10-26 22:55:24 +01:00
cpfeiffer 39c7a853c8 Improve error logging: #153 2015-10-26 22:45:43 +01:00
Carsten Pfeiffer 695f83e19e Merge pull request #152 from aafa/master
ru locale updated
2015-10-26 22:40:12 +01:00
Andreas Shimokawa b99b9bbb75 bump version to 0.6.5, update CHANGELOG.md and README.md 2015-10-25 21:28:35 +09:00
Alexey Afanasev dde32f5a3f ru locale updated 2015-10-25 14:28:08 +03:00
cpfeiffer 54c316778b Add exception to GB.toast() to get it logged 2015-10-25 00:13:32 +02:00
cpfeiffer dd0ba8a230 Updated 2015-10-25 00:10:47 +02:00
cpfeiffer 4200e77016 Disable activity data fetching when not supported
Closes #149
2015-10-24 23:28:55 +02:00
Andreas Shimokawa f287c04ad9 Pebble: simplify Misfit data parsing, since all assumptions seem to be wrong :/ 2015-10-24 11:12:40 +09:00
Andreas Shimokawa 4b9304b897 fix 2015-10-23 23:55:23 +09:00
Andreas Shimokawa 59f4766bc5 no comment 2015-10-23 23:18:36 +09:00
Andreas Shimokawa e809c490dc add method for batch inserts in ActivityDatabaseHandler. Closes #150 2015-10-23 22:13:12 +09:00
cpfeiffer 0cd9b0419c Updated translations from transifex (thanks!) 2015-10-23 00:31:19 +02:00
Andreas Shimokawa 4aff3c8e8e Pebble: try to improve Misfit steps parsing. Should be really close now. 2015-10-22 23:56:45 +09:00
cpfeiffer c350f04fa9 Make "Locate device" work with newer firmware and MI1A. (#136)
Separate the different notification logic into distinct strategy classes.
2015-10-22 00:53:27 +02:00
cpfeiffer 88fb81f921 Make onStartCommand() synchronized, to be sure
(or does Android take care of not calling this concurrently?)
2015-10-22 00:52:45 +02:00
cpfeiffer ac120dc7d6 Small improvement to connect() 2015-10-22 00:32:16 +02:00
cpfeiffer 3b94a96060 Fix some warnings 2015-10-21 23:29:37 +02:00
Andreas Shimokawa 44a36a5f1d Pebble: First try to receive at least steps from the Misfit pebble watchapp 2015-10-21 23:11:59 +09:00
cpfeiffer aa5749cd40 Some improvements to live activity.
Still rather inaccurate due to missing timing information.
2015-10-21 00:36:18 +02:00
cpfeiffer 52f3ca5253 Always name things positively. Negating things is bad for comprehension :-) 2015-10-19 23:36:10 +02:00
cpfeiffer 5a3990b9d2 Guard logging with LOG.isDebugEnabled() 2015-10-19 20:56:56 +02:00
Carsten Pfeiffer 4096e50681 Merge pull request #146 from sarg/master
Fix DeviceInfo checksum function.
2015-10-19 20:56:49 +02:00
Daniele Gobbetti cee03debbb - add code to send the confirmation of the activity data transfer after a timeout.
==> This is currently commented out because it was done to solve #141 but while doing this #143 popped out.
- send a stop_sync message to the band if the data doesn't fit the buffer. This way the data remains on the band.
2015-10-19 16:17:03 +02:00
Daniele Gobbetti 6460b391d9 Be dump the content of the notification to the debug log 2015-10-19 15:16:21 +02:00
Sergey Trofimov 94cbf2f301 Fix DeviceInfo checksum function. 2015-10-19 15:02:56 +03:00
cpfeiffer 4e0fed8857 Improvements to how and when alarms are sent to the device
They are now sent whenever the Alarms activity is finished.
Display "All alarms disabled" when no alarm is enabled.

Unrelated: pass exceptions to GB.toast() where applicable.
2015-10-18 23:52:59 +02:00
cpfeiffer 86d17c7792 Pass the exception to GB.toast() 2015-10-18 23:43:27 +02:00
cpfeiffer ef15bf8ce8 Prevent firmware update for Mi Band 1A for now #136 2015-10-18 22:57:04 +02:00
cpfeiffer 8d62a16176 Update appcompat libs to 23.1.0 2015-10-18 22:56:44 +02:00
cpfeiffer dcd776e09a Attempt to fix never finishing activity data fetching #142
(by disabling unrelated notifications)
2015-10-18 22:27:52 +02:00
cpfeiffer 99470c67ff Added ticket numbers 2015-10-18 21:41:46 +02:00
cpfeiffer 4cdfea25f9 Updated for 0.6.4 (Mi Band 1A) 2015-10-18 21:36:39 +02:00
cpfeiffer 7a44ea9596 Small comment 2015-10-18 21:33:07 +02:00
Carsten Pfeiffer e0289f63ce Merge pull request #145 from sarg/master
Pairing support for MI1A
2015-10-18 21:28:41 +02:00
Sergey Trofimov d57c6166b9 Fix pairing with MI1A 2015-10-18 21:54:22 +03:00
Sergey Trofimov 7591d4a8af Fix typo 2015-10-18 18:29:41 +03:00
Carsten Pfeiffer 1073303849 Merge pull request #144 from sarg/master
Initial support for newer MI devices.
2015-10-18 09:57:13 +02:00
Sergey Trofimov a1fd31c260 Show toast when no valid DeviceSupport found for pairing. 2015-10-18 09:34:51 +03:00
Sergey Trofimov 1c1f8e8535 Make DeviceSupportFactory recognize device names starting with MI 2015-10-18 09:20:18 +03:00
Andreas Shimokawa f0a1d5f8a0 update CHANGELOG.md, bump version, pull translation from transifex (thanks!) 2015-10-18 09:22:11 +09:00
cpfeiffer e755e9f51f updated 2015-10-18 01:40:59 +02:00
cpfeiffer b43e96318a Also support pulldown to sync in ControlCenter #138
Should factor out some common code between ChartsActivity
and ControlCenter, though.
2015-10-18 01:39:25 +02:00
cpfeiffer 1e56e540fa Remove hardcoded equals("MI") in favor of DeviceCoordintator #136 2015-10-18 01:01:13 +02:00
cpfeiffer 2c29384ee8 Updated 2015-10-17 17:11:41 +02:00
cpfeiffer 45fc2c181c Add pulldown to sync and tabs in the charts activity #138 2015-10-17 17:10:14 +02:00
Andreas Shimokawa a9186791dc register/unregister receivers at runtime instead of enabling/disabling them via packagemanger 2015-10-16 00:23:16 +09:00
637 changed files with 55035 additions and 6707 deletions

19
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,19 @@
#### 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!*
#### Your wearable device is:
*Please specify model and firmware version if possible*
#### Your android version is:
#### Your Gadgetbridge version is:
*New issues about already solved/documented topics could be closed without further comments. Same for too generic or incomplete reports.*

2
.gitignore vendored
View File

@ -29,3 +29,5 @@ proguard/
*.iml
MPChartLib
fw.dirs

View File

@ -1,16 +1,25 @@
language: android
jdk:
- oraclejdk8
# disabled -- we now set sourceCompatibility and targetCompatibility appropriately
# - oraclejdk7
env:
- GRADLE_OPTS="-XX:MaxPermSize=256m"
android:
components:
# Uncomment the lines below if you want to
# use the latest revision of Android SDK Tools
# - platform-tools
# - tools
- tools
# The BuildTools version used by your project
- build-tools-23.0.1
- build-tools-25.0.2
# The SDK version used to compile your project
- android-23
- android-25
# Additional components
- extra-android-m2repository
@ -20,3 +29,5 @@ android:
# if you need to run emulator(s) during your tests
#- sys-img-armeabi-v7a-android-19
#- sys-img-x86-android-17
script: ./gradlew build connectedCheck --stacktrace

View File

@ -1,9 +0,0 @@
[main]
host = https://www.transifex.com
[gadgetbridge.strings]
file_filter = app/src/main/res/values-<lang>/strings.xml
source_file = app/src/main/res/values/strings.xml
source_lang = en_US
deviceType = ANDROID
minimum_perc = 50

View File

@ -1,37 +1,589 @@
###Changelog
### Changelog
####Version 0.6.3
#### Version 0.21.5
* Mi2/Bip: Support setting distance units (metric/imperial)
#### Version 0.21.4
* Mi2/Bip: Fix sleep detection for newer firmwares
* Mi2/Bip: Fix ancient bug resulting in wrong activity data at the beginning in diagrams and aggregate data
* No.1 F1: Support setting time format and distance units (metric/imperial)
* Pebble: Support setting distance units to miles for Health (need to reactivate Health in App Manager after toggling)
* HPlus: Make changing distance unit system effective immediately on toggling
#### Version 0.21.3
* Amazfit Bip: Auto-switch language on connect (English, Simplified Chinese, Traditional Chinese), requires FW 0.0.9.14+
#### Version 0.21.2
* Amazfit Bip: Support flashing CEP and ALM files for AGPS
* Amazfit Bip: Initial experimental support for fetching logs from the watch
* Mi2/Bip: Send user info to the device (fixes calories and distance display)
* Mi2/Bip: Fix firmware update progressbar being stuck at the end
* Pebble/Bip: Support more notification icons
* Pebble: Automatically determine color for unknown notifications on Pebble Time
#### Version 0.21.1
* Initial support for EXRIZU K8 (HPLus variant)
* Amazfit Bip: fix long messages not being displayed at all
* Mi Band 2: Support multiple button actions
* NO.1 F1: Fetch sleep data
* NO.1 F1: Heart rate support
* Pebble: Support controlling the current active media playback application
* Fix suspended activities coming to front when rotating the screen
#### Version 0.21.0
* Initial NO.1 F1 support
* Initial Teclast H30 support
* Amazfit Bip: Display GPS firmware version
* Amazfit Bip: Fix E-Mail notifications
* Amazfit Bip: Fix call notification with unknown caller
* Amazfit Bip: Fix crash when weather is updated and device reconnecting
* Mi2/Bip: Fix crash when synchronizing calendar to alarms
* Pebble: Fix crash when takeing screenshots on Android 8.0 (Oreo)
* Pebble: Support some google app icons
* Pebble: try to support spotify
* Mi Band 2: Support configurable button actions
* Fix language being reset to system default
#### Version 0.20.2
* 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)
* Re-enable improved speed zones tab
* Probably fix crash with certain music players
* Improve theme and add changelog icon
#### 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
* 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
* Improve reconnection with BLE devices
* Improve generic notification reliability by trying to restart the notification listener when stale/crashed
* Other small bugfixes
#### 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
* Visual improvements of the pie charts
* Add filter by name in the App blacklist activity
* Pebble: improve compatibility with watch app configuration pages
* Pebble: display battery percentage (will only update once an hour)
* 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
* 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
* Fix bug that caused the same value in weekly charts for every day on Android 6 and older
#### Version 0.18.2
* Mi Band 2: Fix crash on "chat" or "social network" text notification (#603)
#### 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
* All new GUI for the control center
* Add Portuguese pt_PT and pt_BR translations
* Add Czech translation
* Add Hebrew translation and transliteration
* Consistently display device specific icons already during discovery
* Add sleep chart displaying the last week of sleep
* Huge speedup for weekly charts when changing days
* Drop support for importing pre Gadgetbridge 0.12.0 database
* Pebble: allow configuration web pages (clay) to access device location
* Mi Band 2: Initial support for text notifications, caller ID, and icons (requires font installation) (#560)
* Mi Band 2: Support for flashing Mili_pro.ft* font files
* Mi Band 2: Improved firmware/font updated
* Mi Band 2: Set 12h/24h time format, following the Android configuration (#573)
* Improved BLE discovery and connectivity
#### 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
* 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)
* Fixes for cyrillic transliteration
* Pebble: Implement notification privacy modes
* Pebble: Support weather for Obisdian watchface
* Pebble: add a dev option to always and immediately ACK PebbleKit messages to the watch
* HPlus: Support alarms
* HPlus: Fix time and date sync and time format (12/24)
* HPlus: Add device specific preferences and icon
* HPlus: Support for Makibes F68
#### 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
* Pebble: Fix temperature unit in Timestyle Pebble watchface
* Add optional Cyrillic transliteration (for devices lacking the font)
#### 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
* 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
* Pebble: Support for build-in weather system app (FW 4.x)
* Pebble: Add weather support for various watchfaces
* Pebble: Add option to disable call display
* Pebble: Add option to automatically delete notifications that got dismissed on the phone
* Pebble: Bugfix for some PebbleKit enabled 3rd party apps (TCW and maybe other)
* Pebble 2/LE: Improve reliablitly and transfer speed
* HPlus: Improved discovery and pairing
* HPlus: Improved notifications (display + vibration)
* HPlus: Synchronize time and date
* HPlus: Display firmware version and battery charge
* HPlus: Near real time Heart rate measurement
* HPlus: Experimental synchronization of activity data (only sleep, steps and intensity)
* HPlus: Fix some disconnection issues
#### 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
* Charts (Live Activity): Fix axis labels color in dark theme
* Mi Band: Fix ginormous step count when using Live Activity
* Mi Band: Improved performance during activity sync
* Mi Band 2: Fix activity data missing after doing manual hr measurements or live activity
* Support sharing firmwares/watchapps/watchfaces to Gadgetbridge
* Support for the "Subsonic" music player (#474)
#### Version 0.15.2
* Mi Band: Fix crash with unknown notification sources
#### 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
* 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
* Pebble: notification icons for more apps
* 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
* 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
* 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
* Pebble 2: Fix a bug where the Pebble got disconnected by other unrelated LE devices
#### 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)
* Pebble: Further improve compatibility for watchface configuration
* 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
* 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
* Mi Band 2: support for heart rate measurement during sleep
* Mi Band 2: configuration option to activate the display on lift
* 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
* 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
* 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
* 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
* Mi Band 2: Initial support for heart rate readings (Debug activity only)
* Mi Band 2: Support disabled alarms
* Attempt to fix spurious device discovery problems
* Correctly recognize Toffeed, Slimsocial and MaterialFBook as facebook notification sources
#### 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
* 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
* 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
* Possibly fix logging to file on certain devices (#406)
* Mi Band 2: Possibly fix weird connection interdependency between Mi 1 and 2 (#323)
* Mi Band 1S: Whitelist firmware 4.16.4.22
* Mi Band: try application level pairing again, in order to support data sharing with Mi Fit (#250)
* 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
* 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
* 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
* 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
* Initial working Mi Band 2 support (only notifications, no activity and heart rate support)
* Experimental support for Vibratissimo devices
#### 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)
* 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)
* 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
* In Control center, new menu point to launch the new "Database management" activity
* Pebble: Support for Pebble Health on Firmware 4.0
* 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
* Mi Band: support for devices that cannot pair with the band (#349)
#### 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
* Fix activity fetching getting stuck when double tapping (#333)
* Mi Band: in the Device Discovery activity, do not display devices that are already paired
* Mi Band: only allow automatic reconnection on disconnect when the device was previously fully connected
* Mi Band: fix a rare crash when reading data fails due to Bluetooth problems
* Mi Band: log full activity sample to help deciphering activity kinds (#341)
* Mi Band 2: improved discovery mechanism to not rely on MAC addresses (#323)
* 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
* 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
* 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
* 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)
* Mi Band: support for shifting the device time by N hours (for people who sleep at daytime)
* Mi Band: initial and untested support for Mi Band 2
* Allow setting the application language
#### 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
* Fixes for enabling logging at without restarting Gadgetbridge
* Re-enable device paring activity on Android 6 (BLE scanning needs the location preference)
* Display device address in device info
#### 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
* Pebble: enable pbw cache and watchface configuration for Firmware 2.x
* Pebble: allow enabling of Pebble Health without "untested features" being enabled
* Pebble: fix music information being messed up
* Honour "Do Not Disturb" for phone calls and SMS
#### 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
* Mi Band: initial experimental and probably broken support for Amazfit
* 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
* 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
* Several UI Improvements
* Easier First-time setup by using a FAB
* Optional Dark Theme
* Notification App Blacklist is now sorted
* Gadgetbridge Icon in the notification bar displays connection state
* Logging is now configurable without restart
* Mi Band 1S: Initial live heartrate tracking
* Fix certain crash in charts activity on slower devices (#277)
#### 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)
* Fix crash in charts activities when changing the date, quickly (#277)
* Mi Band: preference to enable heart rate measurement during sleep (#232, thanks computerlyrik!)
* Mi Band: display measured heart rate in charts (#232)
* 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
* 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
* Mi Band: Fix update of second (HR) firmware on Mi1S (#234)
* Fix ordering issue of device infos being displayed
#### 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
* 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
* Mi Band: Display HR FW for 1S
* FW and HW versions are only displayed after tapping on the "info" button in Control Center
* 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
* 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
* 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
* 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)
* Pebble: Fix wrong(previous) contact being displayed on the pebble
* Mi Band: improvements to pairing and connecting
* Fix a problem related to shared preferences storage of activity settings
* Very basic support Android 6 runtime permission
* Fix layout of the alarms activity
#### 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
* 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
* 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
* Mi Band: Fix discovery of Mi Band 1S
* Add a confirmation dialog when performing a db import
* Sort blacklist by package names
#### 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
* 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)
* Pebble: Allow installing apps compiled with SDK 2.x also on the basalt platform (Time, Time Steel)
* Pebble: Fix decoding strings in appmessages from the pebble (fixes sending SMS from "Dialer for Pebble")
* Pebble: Support incoming reconnections when device returns from "Airplane Mode" or "Stand-By Mode"
* Pebble: Fix crash when turning off Bluetooth when connected on Android 6.0
* Mi Band: reserve some alarm slots for alerting when upcoming events begin. NB: the band will vibrate at the start time of the event, android reminders are ignored
* Mi Band: Display unique devices Names, not just "MI"
* Some new and updated icons
#### 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
* Pebble: Correct setting the timezone on firmware 3.x (pebble expects the "ID" eg. Europe/Berlin)
* Pebble: Show correct icon for activity tracker and watchfaces in app installer (language and fw icons still missing)
* Pebble: Fix crash when trying to install files though a file manager which are located inside the pbw-cache on firmware 3.x
* Support for deleting all activity data (in the 'Debug' screen)
* Don't pop up the virtual keyboard when entering the Debug screen
* Remove all pending notifications on quit
* 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
* 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)
* Pebble: fix crash on firmware 3.x when pebble requests a pbw that is not in Gadgetbridge's cache
+ Treat Signal notifications as chat notifications
* Fix crash when contacts cannot be read on Android 6.0 (non-granted permissions)
#### 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
* 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
* 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
* 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)
* Mi Band: Attempt at fixing problem with never finishing activity data fetching (#141, #142)
* Register/unregister BroadcastReceivers instead of enabling/disabling them with PackageManager (#134)
(should fix disconnection because the service is being killed)
#### 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
* Mi Band: support firmare versione 1.0.10.14 (and onwards?) vibration
#### 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
* Pebble: WIP implementantion of PebbleKit Intents to make some 3rd party Android apps work with the Pebble (eg. Ventoo)
#### 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 contolling all audio players that react to media buttons (can be chosen in settings)
* Support controlling all audio players that react to media buttons (can be chosen in settings)
* Treat SMS as generic notification if set to "never" (can be blacklisted there also if desired)
* Treat Conversations messagess as chat messages, even if arrived via Pebble Intents (nice icon for Pebble FW 3.x)
* Treat Conversations messages as chat messages, even if arrived via Pebble Intents (nice icon for Pebble FW 3.x)
* 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
@ -39,28 +591,28 @@
* 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
* Graphs now show when the device was not worn by the user (for devices that send this information)
* Remove unused settings option in charts view
* Build target is now Android SDK 23 (Marshmellow)
* 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)
* Pebble: use SMS/EMAIL icons for FW 3.x/Pebble Time
* Pebble: do not throttle notifications
* Support going forward/backwards in time in the activity charts
* Various small bugfixes to the App/Fw Installation Activity
* 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)
@ -70,16 +622,16 @@
* 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)
* 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)
* Mi Band: send the wear location (left / right hand) to the device
* 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
* Set GadgetBridge notification visibility to public, to show the connection status on the lockscreen
#### 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
* Enhancement to activity graphs: new graphs for sleep data (only last night) accessible swiping right from the main graph
@ -88,24 +640,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)
@ -119,33 +671,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
@ -153,39 +705,39 @@
* 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
* Fix for device list not refreshing when Bluetooth gets turned on
* Filter out annoying low battery notifications
* 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
* Added option to start Gadgetbridge and connect automatically when bluetooth is turned on
* stop service if bluetooth is turned off
#### 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

72
CONTRIBUTORS.rst Normal file
View File

@ -0,0 +1,72 @@
.. 2>/dev/null
names ()
{
echo -e "\n exit;\n**Contributors (sorted by number of commits):**\n";
git log --format='%aN:%ae' origin/master | grep -Ev "FYG_.*_bot_ignore_me" | sed 's/@users.github.com/@users.noreply.github.com/g' | awk 'BEGIN{FS=":"}{ct[$1]+=1;if (length($2) > length(e[$1])) {e[$1]=$2}}END{for (i in e) { n[i]=e[i];c[i]+=ct[i] }; for (a in e) print c[a]"\t* "a" <"n[a]">";}' | sort -n -r | cut -f 2-
}
quine ()
{
{
echo ".. 2>/dev/null";
declare -f names | sed -e 's/^[[:space:]]*/ /';
declare -f quine | sed -e 's/^[[:space:]]*/ /';
echo -e " quine\n";
names;
echo -e "\nAnd all the Transifex translators, which I cannot automatically list, at the moment.\n\n*To update the contributors list just run this file with bash*"
} > CONTRIBUTORS.rst;
exit
}
quine
exit;
**Contributors (sorted by number of commits):**
* Andreas Shimokawa <shimokawa@fsfe.org>
* Carsten Pfeiffer <cpfeiffer@users.noreply.github.com>
* Daniele Gobbetti <daniele+github@gobbetti.name>
* João Paulo Barraca <jpbarraca@gmail.com>
* ivanovlev <lion.ivanov@gmal.com>
* Julien Pivotto <roidelapluie@inuits.eu>
* Steffen Liebergeld <perl@gmx.org>
* Lem Dulfo <lemuel.dulfo@gmail.com>
* Sergey Trofimov <sarg@sarg.org.ru>
* JohnnySun <bmy001@gmail.com>
* Uwe Hermann <uwe@hermann-uwe.de>
* Alberto <albertsal83@gmail.com>
* 0nse <0nse@users.noreply.github.com>
* Gergely Peidl <gergely@peidl.net>
* Christian Fischer <sw-dev@computerlyrik.de>
* 6arms1leg <m.brnsfld@googlemail.com>
* walkjivefly <mark@walkjivefly.com>
* Normano64 <per.bergqwist@gmail.com>
* Avamander <Avamander@users.noreply.github.com>
* Ⲇⲁⲛⲓ Φi <daniphii@outlook.com>
* Yar <yaroslav.isakov@gmail.com>
* Yaron Shahrabani <sh.yaron@gmail.com>
* xzovy <caleb@caleb-cooper.net>
* xphnx <xphnx@users.noreply.github.com>
* Tarik Sekmen <tarik@ilixi.org>
* Szymon Tomasz Stefanek <s.stefanek@gmail.com>
* Roman Plevka <rplevka@redhat.com>
* rober <rober@prtl.nodomain.net>
* Nicolò Balzarotti <anothersms@gmail.com>
* Natanael Arndt <arndtn@gmail.com>
* Marc Schlaich <marc.schlaich@googlemail.com>
* kevlarcade <kevlarcade@gmail.com>
* Kevin Richter <me@kevinrichter.nl>
* Kasha <kasha_malaga@hotmail.com>
* Ivan <ivan_tizhanin@mail.ru>
* Hasan Ammar <ammarh@gmail.com>
* Gilles MOREL <contact@gilles-morel.fr>
* Gilles Émilien MOREL <Almtesh@users.noreply.github.com>
* Daniel Hauck <maill@dhauck.eu>
* Chris Perelstein <chris.perelstein@gmail.com>
* Carlos Ferreira <calbertoferreira@gmail.com>
* atkyritsis <at.kyritsis@gmail.com>
* andre <andre.buesgen@yahoo.de>
* Alexey Afanasev <avafanasiev@gmail.com>
And all the Transifex translators, which I cannot automatically list, at the moment.
*To update the contributors list just run this file with bash*

34
FEATURES.md Normal file
View File

@ -0,0 +1,34 @@
## Feature Matrix
| | Pebble OG | Pebble Time/2 | Mi Band | Mi Band 2 | Amazfit Bip |
|-----------------------------------| ----------|---------------|---------|-----------|-------------|
|Calls Notification | YES | YES | YES | YES | YES |
|Reject Calls | YES | YES | NO | NO | YES |
|Accept Calls | NO(2) | NO(2) | NO | NO | NO(3) |
|Generic Notification | YES | YES | YES | YES | YES |
|Dismiss Notifications on Phone | YES | YES | NO | NO | NO |
|Predefined Replies | YES | YES | NO | NO | NO |
|Voice Replies | N/A | NO(3) | N/A | N/A | N/A |
|Calendar Sync | YES | YES | NO | NO | NO |
|Configure alarms from Gadgetbridge | NO | NO | YES | YES | YES |
|Smart alarms | NO(1) | YES | YES | NO | NO |
|Weather | NO(1) | YES | NO | NO | YES |
|Activity Tracking | NO(1) | YES | YES | YES | YES |
|Sleep Tracking | NO(1) | YES | YES | YES | YES |
|HR Tracking | N/A | YES | YES | YES | YES |
|Realtime Activity Tracking | NO | NO | YES | YES | YES |
|Music Control | YES | YES | NO | NO | NO |
|Watchapp/face Installation | YES | YES | NO | NO | NO |
|Firmware Installaton | YES | YES | YES | YES | YES |
|Taking Screenshots | YES | YES | NO | NO | NO |
|Support Android Companion Apps | YES | YES | NO | NO | NO |
(1) Possible via 3rd Party Watchapp
(2) Theoretically possible (works on iOS, would need lot of work)
(3) Possible but not implemented yet
### Notes about Pebble Firmware >=3.0
* Gadgetbridge will keep track of installed watchfaces, but if the Pebble is used with another phone or another app, the information displayed in the app manager can get out of sync since it is impossible to query Firmware >= 3.x for installed apps/watchfaces.

2
GBDaoGenerator/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/bin
/build

View File

@ -0,0 +1,32 @@
apply plugin: 'java'
//apply plugin: 'maven'
apply plugin:'application'
archivesBaseName = 'gadgetbridge-daogenerator'
//version = '0.9.2-SNAPSHOT'
dependencies {
// compile 'org.greenrobot:greendao-generator:2.2.0'
// compile project(":DaoGenerator")
compile 'com.github.freeyourgadget:greendao:1998d7cd2d21f662c6044f6ccf3b3a251bbad341'
}
sourceSets {
main {
java {
srcDir 'src'
}
}
}
mainClassName = "nodomain.freeyourgadget.gadgetbridge.daogen.GBDaoGenerator"
task genSources(type: JavaExec) {
main = mainClassName
classpath = sourceSets.main.runtimeClasspath
workingDir = '../'
}
artifacts {
archives jar
}

View File

@ -0,0 +1,314 @@
/*
* Copyright (C) 2011 Markus Junginger, greenrobot (http://greenrobot.de)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nodomain.freeyourgadget.gadgetbridge.daogen;
import de.greenrobot.daogenerator.DaoGenerator;
import de.greenrobot.daogenerator.Entity;
import de.greenrobot.daogenerator.Index;
import de.greenrobot.daogenerator.Property;
import de.greenrobot.daogenerator.Schema;
/**
* Generates entities and DAOs for the example project DaoExample.
* Automatically run during build.
*/
public class GBDaoGenerator {
private static final String VALID_FROM_UTC = "validFromUTC";
private static final String VALID_TO_UTC = "validToUTC";
private static final String MAIN_PACKAGE = "nodomain.freeyourgadget.gadgetbridge";
private static final String MODEL_PACKAGE = MAIN_PACKAGE + ".model";
private static final String VALID_BY_DATE = MODEL_PACKAGE + ".ValidByDate";
private static final String OVERRIDE = "@Override";
private static final String SAMPLE_RAW_INTENSITY = "rawIntensity";
private static final String SAMPLE_STEPS = "steps";
private static final String SAMPLE_RAW_KIND = "rawKind";
private static final String SAMPLE_HEART_RATE = "heartRate";
private static final String TIMESTAMP_FROM = "timestampFrom";
private static final String TIMESTAMP_TO = "timestampTo";
public static void main(String[] args) throws Exception {
Schema schema = new Schema(17, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
Entity deviceAttributes = addDeviceAttributes(schema);
Entity device = addDevice(schema, deviceAttributes);
// yeah deep shit, has to be here (after device) for db upgrade and column order
// because addDevice adds a property to deviceAttributes also....
deviceAttributes.addStringProperty("volatileIdentifier");
Entity tag = addTag(schema);
Entity userDefinedActivityOverlay = addActivityDescription(schema, tag, user);
addMiBandActivitySample(schema, user, device);
addPebbleHealthActivitySample(schema, user, device);
addPebbleHealthActivityKindOverlay(schema, user, device);
addPebbleMisfitActivitySample(schema, user, device);
addPebbleMorpheuzActivitySample(schema, user, device);
addHPlusHealthActivityKindOverlay(schema, user, device);
addHPlusHealthActivitySample(schema, user, device);
addNo1F1ActivitySample(schema, user, device);
addCalendarSyncState(schema, device);
new DaoGenerator().generateAll(schema, "app/src/main/java");
}
private static Entity addTag(Schema schema) {
Entity tag = addEntity(schema, "Tag");
tag.addIdProperty();
tag.addStringProperty("name").notNull();
tag.addStringProperty("description").javaDocGetterAndSetter("An optional description of this tag.");
tag.addLongProperty("userId").notNull();
return tag;
}
private static Entity addActivityDescription(Schema schema, Entity tag, Entity user) {
Entity activityDesc = addEntity(schema, "ActivityDescription");
activityDesc.setJavaDoc("A user may further specify his activity with a detailed description and the help of tags.\nOne or more tags can be added to a given activity range.");
activityDesc.addIdProperty();
activityDesc.addIntProperty(TIMESTAMP_FROM).notNull();
activityDesc.addIntProperty(TIMESTAMP_TO).notNull();
activityDesc.addStringProperty("details").javaDocGetterAndSetter("An optional detailed description, specific to this very activity occurrence.");
Property userId = activityDesc.addLongProperty("userId").notNull().getProperty();
activityDesc.addToOne(user, userId);
Entity activityDescTagLink = addEntity(schema, "ActivityDescTagLink");
activityDescTagLink.addIdProperty();
Property sourceId = activityDescTagLink.addLongProperty("activityDescriptionId").notNull().getProperty();
Property targetId = activityDescTagLink.addLongProperty("tagId").notNull().getProperty();
activityDesc.addToMany(tag, activityDescTagLink, sourceId, targetId);
return activityDesc;
}
private static Entity addUserInfo(Schema schema, Entity userAttributes) {
Entity user = addEntity(schema, "User");
user.addIdProperty();
user.addStringProperty("name").notNull();
user.addDateProperty("birthday").notNull();
user.addIntProperty("gender").notNull();
Property userId = userAttributes.addLongProperty("userId").notNull().getProperty();
// sorted by the from-date, newest first
Property userAttributesSortProperty = getPropertyByName(userAttributes, VALID_FROM_UTC);
user.addToMany(userAttributes, userId).orderDesc(userAttributesSortProperty);
return user;
}
private static Property getPropertyByName(Entity entity, String propertyName) {
for (Property prop : entity.getProperties()) {
if (propertyName.equals(prop.getPropertyName())) {
return prop;
}
}
throw new IllegalStateException("Could not find property " + propertyName + " in entity " + entity.getClassName());
}
private static Entity addUserAttributes(Schema schema) {
// additional properties of a user, which may change during the lifetime of a user
// this allows changing attributes while preserving user identity
Entity userAttributes = addEntity(schema, "UserAttributes");
userAttributes.addIdProperty();
userAttributes.addIntProperty("heightCM").notNull();
userAttributes.addIntProperty("weightKG").notNull();
userAttributes.addIntProperty("sleepGoalHPD").javaDocGetterAndSetter("Desired number of hours of sleep per day.");
userAttributes.addIntProperty("stepsGoalSPD").javaDocGetterAndSetter("Desired number of steps per day.");
addDateValidityTo(userAttributes);
return userAttributes;
}
private static void addDateValidityTo(Entity entity) {
entity.addDateProperty(VALID_FROM_UTC).codeBeforeGetter(OVERRIDE);
entity.addDateProperty(VALID_TO_UTC).codeBeforeGetter(OVERRIDE);
entity.implementsInterface(VALID_BY_DATE);
}
private static Entity addDevice(Schema schema, Entity deviceAttributes) {
Entity device = addEntity(schema, "Device");
device.addIdProperty();
device.addStringProperty("name").notNull();
device.addStringProperty("manufacturer").notNull();
device.addStringProperty("identifier").notNull().unique().javaDocGetterAndSetter("The fixed identifier, i.e. MAC address of the device.");
device.addIntProperty("type").notNull().javaDocGetterAndSetter("The DeviceType key, i.e. the GBDevice's type.");
device.addStringProperty("model").javaDocGetterAndSetter("An optional model, further specifying the kind of device-");
Property deviceId = deviceAttributes.addLongProperty("deviceId").notNull().getProperty();
// sorted by the from-date, newest first
Property deviceAttributesSortProperty = getPropertyByName(deviceAttributes, VALID_FROM_UTC);
device.addToMany(deviceAttributes, deviceId).orderDesc(deviceAttributesSortProperty);
return device;
}
private static Entity addDeviceAttributes(Schema schema) {
Entity deviceAttributes = addEntity(schema, "DeviceAttributes");
deviceAttributes.addIdProperty();
deviceAttributes.addStringProperty("firmwareVersion1").notNull();
deviceAttributes.addStringProperty("firmwareVersion2");
addDateValidityTo(deviceAttributes);
return deviceAttributes;
}
private static Entity addMiBandActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "MiBandActivitySample");
activitySample.implementsSerializable();
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
addHeartRateProperties(activitySample);
return activitySample;
}
private static void addHeartRateProperties(Entity activitySample) {
activitySample.addIntProperty(SAMPLE_HEART_RATE).notNull().codeBeforeGetterAndSetter(OVERRIDE);
}
private static Entity addPebbleHealthActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "PebbleHealthActivitySample");
addCommonActivitySampleProperties("AbstractPebbleHealthActivitySample", activitySample, user, device);
activitySample.addByteArrayProperty("rawPebbleHealthData").codeBeforeGetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
addHeartRateProperties(activitySample);
return activitySample;
}
private static Entity addPebbleHealthActivityKindOverlay(Schema schema, Entity user, Entity device) {
Entity activityOverlay = addEntity(schema, "PebbleHealthActivityOverlay");
activityOverlay.addIntProperty(TIMESTAMP_FROM).notNull().primaryKey();
activityOverlay.addIntProperty(TIMESTAMP_TO).notNull().primaryKey();
activityOverlay.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
Property deviceId = activityOverlay.addLongProperty("deviceId").primaryKey().notNull().getProperty();
activityOverlay.addToOne(device, deviceId);
Property userId = activityOverlay.addLongProperty("userId").notNull().getProperty();
activityOverlay.addToOne(user, userId);
activityOverlay.addByteArrayProperty("rawPebbleHealthData");
return activityOverlay;
}
private static Entity addPebbleMisfitActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "PebbleMisfitSample");
addCommonActivitySampleProperties("AbstractPebbleMisfitActivitySample", activitySample, user, device);
activitySample.addIntProperty("rawPebbleMisfitSample").notNull().codeBeforeGetter(OVERRIDE);
return activitySample;
}
private static Entity addPebbleMorpheuzActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "PebbleMorpheuzSample");
addCommonActivitySampleProperties("AbstractPebbleMorpheuzActivitySample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
return activitySample;
}
private static Entity addHPlusHealthActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "HPlusHealthActivitySample");
activitySample.implementsSerializable();
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
activitySample.addByteArrayProperty("rawHPlusHealthData");
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
addHeartRateProperties(activitySample);
activitySample.addIntProperty("distance");
activitySample.addIntProperty("calories");
return activitySample;
}
private static Entity addHPlusHealthActivityKindOverlay(Schema schema, Entity user, Entity device) {
Entity activityOverlay = addEntity(schema, "HPlusHealthActivityOverlay");
activityOverlay.addIntProperty(TIMESTAMP_FROM).notNull().primaryKey();
activityOverlay.addIntProperty(TIMESTAMP_TO).notNull().primaryKey();
activityOverlay.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey();
Property deviceId = activityOverlay.addLongProperty("deviceId").primaryKey().notNull().getProperty();
activityOverlay.addToOne(device, deviceId);
Property userId = activityOverlay.addLongProperty("userId").notNull().getProperty();
activityOverlay.addToOne(user, userId);
activityOverlay.addByteArrayProperty("rawHPlusHealthData");
return activityOverlay;
}
private static Entity addNo1F1ActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "No1F1ActivitySample");
activitySample.implementsSerializable();
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
addHeartRateProperties(activitySample);
return activitySample;
}
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
activitySample.setSuperclass(superClass);
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
activitySample.setJavaDoc(
"This class represents a sample specific to the device. Values like activity kind or\n" +
"intensity, are device specific. Normalized values can be retrieved through the\n" +
"corresponding {@link SampleProvider}.");
activitySample.addIntProperty("timestamp").notNull().codeBeforeGetterAndSetter(OVERRIDE).primaryKey();
Property deviceId = activitySample.addLongProperty("deviceId").primaryKey().notNull().codeBeforeGetterAndSetter(OVERRIDE).getProperty();
activitySample.addToOne(device, deviceId);
Property userId = activitySample.addLongProperty("userId").notNull().codeBeforeGetterAndSetter(OVERRIDE).getProperty();
activitySample.addToOne(user, userId);
}
private static void addCalendarSyncState(Schema schema, Entity device) {
Entity calendarSyncState = addEntity(schema, "CalendarSyncState");
calendarSyncState.addIdProperty();
Property deviceId = calendarSyncState.addLongProperty("deviceId").notNull().getProperty();
Property calendarEntryId = calendarSyncState.addLongProperty("calendarEntryId").notNull().getProperty();
Index indexUnique = new Index();
indexUnique.addProperty(deviceId);
indexUnique.addProperty(calendarEntryId);
indexUnique.makeUnique();
calendarSyncState.addIndex(indexUnique);
calendarSyncState.addToOne(device, deviceId);
calendarSyncState.addIntProperty("hash").notNull();
}
private static Property findProperty(Entity entity, String propertyName) {
for (Property prop : entity.getProperties()) {
if (propertyName.equals(prop.getPropertyName())) {
return prop;
}
}
throw new IllegalArgumentException("Property " + propertyName + " not found in Entity " + entity.getClassName());
}
private static Entity addEntity(Schema schema, String className) {
Entity entity = schema.addEntity(className);
entity.addImport("de.greenrobot.dao.AbstractDao");
return entity;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -1,13 +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):
ic_device_pebble.png (by xphnx)
ic_device_miband.png (by xphnx)
ic_activitytracker.png (by xphnx)
ic_watchface.png (by xphnx)
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-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/

128
README.md
View File

@ -1,9 +1,17 @@
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.
Gadgetbridge is an Android (4.4+) application which will allow you to use your
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)
[![Donate](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/Gadgetbridge/donate)
[![Build](https://travis-ci.org/Freeyourgadget/Gadgetbridge.svg?branch=master)](https://travis-ci.org/Freeyourgadget/Gadgetbridge)
@ -11,103 +19,99 @@ 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)
## Features (Pebble)
## 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
* 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)
* Teclast H30 (WIP)
* NO.1 F1 (WIP)
* Liveview
* Vibratissimo (experimental)
* Incoming calls notification and display (caller, phone number)
* Outgoing call display
* Reject/hangup calls
* SMS notification (sender, body)
* K-9 Mail notification support (sender, subject, preview)
* Support for generic notifications (above filtered out)
* Dismiss individial notifications or open corresponding app on phone from the action menu (generic notifications)
* Dismiss all notifications from the action menu (non-generic notifications)
* Music playback info (artist, album, track)
* Music control: play/pause, next track, previous track, volume up, volume down
* List and remove installed apps/watchfaces
* Install watchfaces and firmware files (.pbw and .pbz)
* Install language files (.pbl) (currently on Firmware 2.x only)
* Take and share screenshots from the Pebble's screen
* PebbleKit support for 3rd Party Android Apps support (experimental).
* Morpheuz sleep data syncronization (experimental)
## Features
## Notes about the Pebble Time
Please see [FEATURES.md](https://github.com/Freeyourgadget/Gadgetbridge/blob/master/FEATURES.md)
All features are also supported on the Pebble Time, except for the following:
## Getting Started (Pebble)
* Listing installed watchfaces (it will simply display the UUIDs of previously installed watchapps, no matter if they are still installed or not)
* Firmware installation is untested and will probably not work.
## How to use (Pebble)
1. Pair your Pebble through Gadgetbridge's Discovery Activity or the Android Bluetooth Settings
1. Pair your Pebble through the Android's Bluetooth Settings or Gadgetbridge. Pebble 2 MUST be paired though Gadgetbridge (tap on the + in Control Center)
2. Start Gadgetbridge, tap on the device you want to connect to
3. To test, choose "Debug" from the menu and play around
## Features (Mi Band)
For more information read [this wiki article](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble-Getting-Started)
* Mi Band notifications (LEDs + vibration) for
* Discovery and pairing
* Incoming calls
* SMS received
* K-9 mails received
* Generic Android notifications
* Synchronize the time to the Mi Band
* Display firmware version and battery state
* Synchronize activity data
* Display sleep data (alpha)
* Display sports data (step count) (alpha)
* Set alarms on the Mi Band
## How to use (Mi Band 1+2)
## How to use (Mi Band)
* When starting Gadgetbridge and no device is visible, it will automatically
attempt to discover and pair your Mi Band. Alternatively you can invoke this
manually via the menu button. It will ask you for some personal info that appears
* When starting Gadgetbridge the first time, it will automatically
attempt to discover and pair your Mi Band. Alternatively you can invoke discovery
manually via the "+" button. It will ask you for some personal info that appears
to be needed for proper steps calculation on the band. If you do not provide these,
some hardcoded default "dummy" values will be used instead.
When your Mi Band starts to vibrate and blink with all three LEDs during the pairing process,
When your Mi Band starts to vibrate and blink during the pairing process,
tap it quickly a few times in a row to confirm the pairing with the band.
1. Configure other notifications as desired
2. Go back to the "Gadgetbridge" Activity
3. Tap the "MI" item to connect if you're not connected yet.
2. Go back to the "Gadgetbridge" activity
3. Tap the Mi Band item to connect if you're not connected yet
4. To test, chose "Debug" from the menu and play around
Known Issues:
* Android 4.4+ only, we can only change this by not handling generic
notifications or by using AccessibiltyService. Don't know if it is worth the
hassle.
**Known Issues:**
* The initial connection to a Mi Band sometimes takes a little patience. Try to connect a few times, wait,
and try connecting again. This only happens until you have "bonded" with the Mi Band, i.e. until it
knows your MAC address. This behavior may also only occur with older firmware versions.
* If you use other apps like Mi Fit, and "bonding" with Gadgetbridge does not work, please
try to unpair the band in the other app and try again with Gadgetbridge.
* While all Mi Band devices are supported, some firmware versions might work better than others.
You can consult the [projects wiki pages](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band)
to check if your firmware version is fully supported or if an upgrade/downgrade might be beneficial.
## Authors (in order of first code contribution)
## Features (Liveview)
* set time (automatically upon connection)
* display notifications and vibrate
## Authors
### Core Team (in order of first code contribution)
* Andreas Shimokawa
* Carsten Pfeiffer
* Daniele Gobbetti
### Additional device support
* João Paulo Barraca (HPlus)
* Vitaly Svyastyn (NO.1 F1)
* Sami Alaoui (Teclast H30)
## Contribute
Contributions are welcome, be it feedback, bugreports, documentation, translation, research or code. Feel free to work
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?
Feel free to open an issue on our issue tracker, but please:
- do not use the issue tracker as a forum, do not ask for ETAs and read the issue conversation before posting
- use the search functionality to ensure that your question wasn't already answered. Don't forget to check the **closed** issues as well!
- remember that this is a community project, people are contributing in their free time because they like doing so: don't take the fun away! Be kind and constructive.
## Having problems?
0. Phone crashing during device discovery? Disable Privacy Guard (or similarly named functionality) during discovery.
1. Open Gadgetbridge's settings and check the option to write log files
2. Quit Gadgetbridge and restart it
3. Reproduce the problem you encountered
4. Check the logfile at /sdcard/Android/data/nodomain.freeyourgadget.gadgetbridge/files/gadgetbridge.log
5. File an issue at https://github.com/Freeyourgadget/Gadgetbridge/issues/new and possibly provide the logfile
2. Reproduce the problem you encountered
3. Check the logfile at /sdcard/Android/data/nodomain.freeyourgadget.gadgetbridge/files/gadgetbridge.log
4. File an issue at https://github.com/Freeyourgadget/Gadgetbridge/issues/new and possibly provide the logfile
Alternatively you may use the standard logcat functionality to access the log.

View File

@ -1,19 +1,34 @@
apply plugin: 'com.android.application'
apply plugin: 'findbugs'
apply plugin: 'pmd'
def ABORT_ON_CHECK_FAILURE=false
tasks.withType(Test) {
systemProperty 'MiFirmwareDir', System.getProperty('MiFirmwareDir', null)
systemProperty 'logback.configurationFile', System.getProperty('user.dir', null) + '/app/src/main/assets/logback.xml'
systemProperty 'GB_LOGFILES_DIR', java.nio.file.Files.createTempDirectory('gblog').toString();
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
compileOptions {
// for KitKat
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
compileSdkVersion 25
buildToolsVersion '25.0.2'
defaultConfig {
applicationId "nodomain.freeyourgadget.gadgetbridge"
minSdkVersion 19
targetSdkVersion 23
versionCode 29
versionName "0.6.3"
targetSdkVersion 25
// note: always bump BOTH versionCode and versionName!
versionName "0.21.5"
versionCode 106
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
@ -37,17 +52,42 @@ android {
}
}
pmd {
toolVersion = '5.5.5'
}
dependencies {
// testCompile 'ch.qos.logback:logback-classic:1.1.3'
// testCompile 'ch.qos.logback:logback-core:1.1.3'
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:1.9.5"
testCompile "org.robolectric:robolectric:3.3.2"
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.0.0'
compile 'com.android.support:support-v4:23.0.0'
compile 'com.github.tony19:logback-android-classic:1.1.1-3'
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:cardview-v7:25.3.1'
compile 'com.android.support:recyclerview-v7:25.3.1'
compile 'com.android.support:support-v4:25.3.1'
compile 'com.android.support:gridlayout-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:palette-v7:25.3.1'
compile 'com.github.tony19:logback-android-classic:1.1.1-6'
compile 'org.slf4j:slf4j-api:1.7.7'
compile 'com.github.PhilJay:MPAndroidChart:v2.1.4'
compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'
compile 'com.github.pfichtner:durationformatter:0.1.1'
compile 'de.cketti.library.changelog:ckchangelog:1.2.2'
compile 'net.e175.klaus:solarpositioning:0.0.9'
// use pristine greendao instead of our custom version, since our custom jitpack-packaged
// version contains way too much and our custom patches are in the generator only.
compile 'org.greenrobot:greendao:2.2.1'
compile 'org.apache.commons:commons-lang3:3.5'
// compile project(":DaoCore")
}
preBuild.dependsOn(":GBDaoGenerator:genSources")
gradle.beforeProject {
preBuild.dependsOn(":GBDaoGenerator:genSources")
}
check.dependsOn 'findbugs', 'pmd', 'lint'
@ -93,7 +133,6 @@ task pmd(type: Pmd) {
}
}
task findbugs(type: FindBugs) {
ignoreFailures = !ABORT_ON_CHECK_FAILURE
effort = "default"

View File

@ -2,21 +2,22 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="nodomain.freeyourgadget.gadgetbridge">
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="23" />
<!--
<uses-permission android:name="android.permission.INTERNET" />
-->
<!--
Comment in for testing Pebble Emulator
<uses-permission android:name="android.permission.INTERNET" />
-->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.fsck.k9.permission.READ_MESSAGES" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-feature
android:name="android.hardware.bluetooth"
@ -24,7 +25,6 @@
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" />
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
@ -37,51 +37,43 @@
android:label="@string/app_name"
android:theme="@style/GadgetbridgeTheme">
<activity
android:name=".activities.ControlCenter"
android:label="@string/title_activity_controlcenter">
android:name=".activities.ControlCenterv2"
android:label="@string/title_activity_controlcenter"
android:theme="@style/GadgetbridgeTheme.NoActionBar">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activities.SettingsActivity"
android:label="@string/title_activity_settings">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.ControlCenter" />
</activity>
android:label="@string/title_activity_settings"
android:parentActivityName=".activities.ControlCenterv2" />
<activity
android:name=".devices.miband.MiBandPreferencesActivity"
android:label="@string/preferences_miband_settings">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.SettingsActivity" />
</activity>
android:label="@string/preferences_miband_settings"
android:parentActivityName=".activities.SettingsActivity" />
<activity
android:name=".activities.AppManagerActivity"
android:label="@string/title_activity_appmanager">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.ControlCenter" />
</activity>
android:launchMode="singleTop"
android:name=".activities.appmanager.AppManagerActivity"
android:label="@string/title_activity_appmanager"
android:parentActivityName=".activities.ControlCenterv2" />
<activity
android:name=".activities.AppBlacklistActivity"
android:label="@string/title_activity_appblacklist">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.SettingsActivity" />
</activity>
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:label="@string/title_activity_fw_app_insaller">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.ControlCenter" />
android:label="@string/title_activity_fw_app_insaller"
android:parentActivityName=".activities.ControlCenterv2">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
@ -102,7 +94,46 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\.ft" />
<data android:pathPattern="/.*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<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" />
@ -113,7 +144,6 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
<data android:pathPattern="/.*\\.pbz" />
<data android:pathPattern="/.*\\..*\\.pbz" />
<data android:pathPattern="/.*\\..*\\..*\\.pbz" />
@ -124,7 +154,6 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
<data android:pathPattern="/.*\\.pbl" />
<data android:pathPattern="/.*\\..*\\.pbl" />
<data android:pathPattern="/.*\\..*\\..*\\.pbl" />
@ -138,6 +167,7 @@
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<!-- no mimeType filter, needed for CM-derived ROMs? -->
@ -157,7 +187,46 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft.en" />
<data android:pathPattern="/.*\\.ft" />
<data android:pathPattern="/.*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
<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" />
@ -168,7 +237,6 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
<data android:pathPattern="/.*\\.pbz" />
<data android:pathPattern="/.*\\..*\\.pbz" />
<data android:pathPattern="/.*\\..*\\..*\\.pbz" />
@ -179,7 +247,6 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
<data android:pathPattern="/.*\\.pbl" />
<data android:pathPattern="/.*\\..*\\.pbl" />
<data android:pathPattern="/.*\\..*\\..*\\.pbl" />
@ -192,13 +259,32 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbl" />
</intent-filter>
<!-- to receive the firmwares from the donwload content provider -->
<!-- to receive the firmwares from the download content provider -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/octet-stream" />
</intent-filter>
<!-- to receive firmwares from the download content provider if recognized as zip-->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/zip" />
<data android:mimeType="application/x-zip-compressed" />
</intent-filter>
<!-- to receive files from the "share" intent -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<service
@ -209,56 +295,31 @@
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
<service android:name=".service.NotificationCollectorMonitorService" />
<service android:name=".service.DeviceCommunicationService" />
<receiver
android:name=".externalevents.PhoneCallReceiver"
android:enabled="false">
android:name=".externalevents.WeatherNotificationReceiver"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
<action android:name="ru.gelin.android.weather.notification.ACTION_WEATHER_UPDATE_2" />
</intent-filter>
</receiver>
<receiver
android:name=".externalevents.SMSReceiver"
android:enabled="false">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
<receiver
android:name=".externalevents.TimeChangeReceiver"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
<action android:name="android.intent.action.TIME_SET" />
</intent-filter>
</receiver>
<receiver
android:name=".externalevents.K9Receiver"
android:enabled="false">
<intent-filter>
<data android:scheme="email" />
<action android:name="com.fsck.k9.intent.action.EMAIL_RECEIVED" />
</intent-filter>
</receiver>
<receiver
android:name=".externalevents.PebbleReceiver"
android:enabled="false">
<activity android:name=".externalevents.WeatherNotificationConfig">
<intent-filter>
<action android:name="com.getpebble.action.SEND_NOTIFICATION" />
<action android:name="ru.gelin.android.weather.notification.ACTION_WEATHER_SKIN_PREFERENCES"/>
</intent-filter>
</receiver>
<receiver
android:name=".externalevents.MusicPlaybackReceiver"
android:enabled="false">
</activity>
<receiver android:name=".externalevents.AutoStartReceiver"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="com.android.music.metachanged"/>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver
android:name=".externalevents.BluetoothStateChangeReceiver"
android:exported="false">
@ -281,50 +342,100 @@
</intent-filter>
</receiver>
<!--
forcing the DebugActivity to portrait mode avoids crashes with the progress
dialog when changing orientation
-->
<activity
android:name=".activities.DebugActivity"
android:label="@string/title_activity_debug">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.ControlCenter" />
</activity>
android:label="@string/title_activity_debug"
android:parentActivityName=".activities.ControlCenterv2"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".activities.DbManagementActivity"
android:label="@string/title_activity_db_management"
android:parentActivityName=".activities.ControlCenterv2"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".activities.DiscoveryActivity"
android:label="@string/title_activity_discovery">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.ControlCenter" />
</activity>
android:label="@string/title_activity_discovery"
android:parentActivityName=".activities.ControlCenterv2" />
<activity
android:name=".activities.AndroidPairingActivity"
android:label="@string/title_activity_android_pairing" />
<activity
android:name=".devices.miband.MiBandPairingActivity"
android:label="@string/title_activity_mi_band_pairing" />
<activity
android:name=".devices.pebble.PebblePairingActivity"
android:label="@string/title_activity_pebble_pairing" />
<activity
android:name=".activities.charts.ChartsActivity"
android:label="@string/title_activity_charts"
android:parentActivityName=".activities.ControlCenter">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.ControlCenter" />
</activity>
android:parentActivityName=".activities.ControlCenterv2" />
<activity
android:name=".activities.ConfigureAlarms"
android:label="@string/title_activity_set_alarm"
android:parentActivityName=".activities.SettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.SettingsActivity" />
</activity>
android:parentActivityName=".activities.SettingsActivity" />
<activity
android:name=".activities.AlarmDetails"
android:label="@string/title_activity_alarm_details"
android:parentActivityName=".activities.ConfigureAlarms">
android:screenOrientation="portrait"
android:parentActivityName=".activities.ConfigureAlarms" />
<activity
android:name=".activities.VibrationActivity"
android:label="@string/title_activity_vibration"
android:parentActivityName=".activities.ControlCenterv2" />
<activity
android:name=".activities.AudioSettingsActivity"
android:label="@string/title_audio_activity"
android:parentActivityName=".activities.ControlCenterv2" />
<provider
android:name=".contentprovider.PebbleContentProvider"
android:authorities="com.getpebble.android.provider"
android:exported="true" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.screenshot_provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/screenshot_provider_paths"/>
</provider>
<receiver android:name=".SleepAlarmWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="nodomain.freeyourgadget.gadgetbridge.SLEEP_ALARM_WIDGET_CLICK" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/sleep_alarm_widget_info" />
</receiver>
<activity
android:launchMode="singleTask"
android:allowTaskReparenting="true"
android:clearTaskOnLaunch="true"
android:name=".activities.ExternalPebbleJSActivity"
android:label="@string/app_configure"
android:parentActivityName=".activities.appmanager.AppManagerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms" />
android:value="nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2" />
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="gadgetbridge" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,102 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8"/>
<meta name='viewport' content='initial-scale=1.0, maximum-scale=1.0'>
<script type="text/javascript" src="js/Uri.js">
</script>
<script type="text/javascript" src="js/gadgetbridge_boilerplate.js">
</script>
<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;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-touch-callout: none;
}
#config_url,#jsondata {
word-wrap: break-word;
margin: 20px 0;
width: 90%;
}
.btn {
display: inline-block;
position: relative;
height: 32px;
line-height: 32px;
border-radius: 2px;
font-size: 0.9em;
background-color: #eee;
color: #646464;
text-align: center;
border-style: none;
}
.btn:active {
border-style: none;
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2)inset;
transition-delay: 0s;
}
p {
width: 90%;
}
#pastereturn {
width: 90%;
min-height: 3em;
}
#step1compat, #step2 {
display: none;
}
<!-- TODO -->
</style>
</head>
<body>
<div id="step1" class="step">
<h2>URL of the configuration:</h2>
<div id="config_url"></div>
<!--<button class="btn" name="show config" value="show config" onclick="Pebble.showConfiguration()" >Show config / URL</button>-->
<button class="btn" name="open config" value="open config" onclick="Pebble.actuallyOpenURL()">
Open configuration website
</button>
<h2 class="load_presets">App presets:</h2>
<button class="btn load_presets" name="read config" value="read config"
onclick="Pebble.loadPreset()">
Load saved configuration
</button>
</div>
<div id="step1compat" class="step">
<p>In case of "network error" after saving settings in the watchapp, copy the "network error"
URL and paste it here:</p>
<textarea id="pastereturn"></textarea><br/>
<button class="btn" name="parse" onclick="Pebble.parseReturnedPebbleJS()">Parse legacy app
configuration
</button>
</div>
<div id="step2" class="step">
<h2>Incoming configuration data:</h2>
<div id="jsondata"></div>
<button class="btn" name="send config" value="send config" onclick="Pebble.actuallySendData()">
Send data to pebble
</button>
<h2 class="store_presets">App Presets:</h2>
<button class="btn store_presets" name="store config" value="store config"
onclick="Pebble.savePreset()">
Store incoming configuration
</button>
<p class="store_presets">Existing presets will be deleted.</p>
</div>
</body>

View File

@ -0,0 +1,458 @@
/*!
* jsUri
* https://github.com/derek-watson/jsUri
*
* Copyright 2013, Derek Watson
* Released under the MIT license.
*
* Includes parseUri regular expressions
* http://blog.stevenlevithan.com/archives/parseuri
* Copyright 2007, Steven Levithan
* Released under the MIT license.
*/
/*globals define, module */
(function(global) {
var re = {
starts_with_slashes: /^\/+/,
ends_with_slashes: /\/+$/,
pluses: /\+/g,
query_separator: /[&;]/,
uri_parser: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*)(?::([^:@\/]*))?)?@)?(\[[0-9a-fA-F:.]+\]|[^:\/?#]*)(?::(\d+|(?=:)))?(:)?)((((?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
};
/**
* Define forEach for older js environments
* @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach#Compatibility
*/
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(callback, thisArg) {
var T, k;
if (this == null) {
throw new TypeError(' this is null or not defined');
}
var O = Object(this);
var len = O.length >>> 0;
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
T = thisArg;
}
k = 0;
while (k < len) {
var kValue;
if (k in O) {
kValue = O[k];
callback.call(T, kValue, k, O);
}
k++;
}
};
}
/**
* unescape a query param value
* @param {string} s encoded value
* @return {string} decoded value
*/
function decode(s) {
if (s) {
s = s.toString().replace(re.pluses, '%20');
s = decodeURIComponent(s);
}
return s;
}
/**
* Breaks a uri string down into its individual parts
* @param {string} str uri
* @return {object} parts
*/
function parseUri(str) {
var parser = re.uri_parser;
var parserKeys = ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "isColonUri", "relative", "path", "directory", "file", "query", "anchor"];
var m = parser.exec(str || '');
var parts = {};
parserKeys.forEach(function(key, i) {
parts[key] = m[i] || '';
});
return parts;
}
/**
* Breaks a query string down into an array of key/value pairs
* @param {string} str query
* @return {array} array of arrays (key/value pairs)
*/
function parseQuery(str) {
var i, ps, p, n, k, v, l;
var pairs = [];
if (typeof(str) === 'undefined' || str === null || str === '') {
return pairs;
}
if (str.indexOf('?') === 0) {
str = str.substring(1);
}
ps = str.toString().split(re.query_separator);
for (i = 0, l = ps.length; i < l; i++) {
p = ps[i];
n = p.indexOf('=');
if (n !== 0) {
k = decode(p.substring(0, n));
v = decode(p.substring(n + 1));
pairs.push(n === -1 ? [p, null] : [k, v]);
}
}
return pairs;
}
/**
* Creates a new Uri object
* @constructor
* @param {string} str
*/
function Uri(str) {
this.uriParts = parseUri(str);
this.queryPairs = parseQuery(this.uriParts.query);
this.hasAuthorityPrefixUserPref = null;
}
/**
* Define getter/setter methods
*/
['protocol', 'userInfo', 'host', 'port', 'path', 'anchor'].forEach(function(key) {
Uri.prototype[key] = function(val) {
if (typeof val !== 'undefined') {
this.uriParts[key] = val;
}
return this.uriParts[key];
};
});
/**
* if there is no protocol, the leading // can be enabled or disabled
* @param {Boolean} val
* @return {Boolean}
*/
Uri.prototype.hasAuthorityPrefix = function(val) {
if (typeof val !== 'undefined') {
this.hasAuthorityPrefixUserPref = val;
}
if (this.hasAuthorityPrefixUserPref === null) {
return (this.uriParts.source.indexOf('//') !== -1);
} else {
return this.hasAuthorityPrefixUserPref;
}
};
Uri.prototype.isColonUri = function (val) {
if (typeof val !== 'undefined') {
this.uriParts.isColonUri = !!val;
} else {
return !!this.uriParts.isColonUri;
}
};
/**
* Serializes the internal state of the query pairs
* @param {string} [val] set a new query string
* @return {string} query string
*/
Uri.prototype.query = function(val) {
var s = '', i, param, l;
if (typeof val !== 'undefined') {
this.queryPairs = parseQuery(val);
}
for (i = 0, l = this.queryPairs.length; i < l; i++) {
param = this.queryPairs[i];
if (s.length > 0) {
s += '&';
}
if (param[1] === null) {
s += param[0];
} else {
s += param[0];
s += '=';
if (typeof param[1] !== 'undefined') {
s += encodeURIComponent(param[1]);
}
}
}
return s.length > 0 ? '?' + s : s;
};
/**
* returns the first query param value found for the key
* @param {string} key query key
* @return {string} first value found for key
*/
Uri.prototype.getQueryParamValue = function (key) {
var param, i, l;
for (i = 0, l = this.queryPairs.length; i < l; i++) {
param = this.queryPairs[i];
if (key === param[0]) {
return param[1];
}
}
};
/**
* returns an array of query param values for the key
* @param {string} key query key
* @return {array} array of values
*/
Uri.prototype.getQueryParamValues = function (key) {
var arr = [], i, param, l;
for (i = 0, l = this.queryPairs.length; i < l; i++) {
param = this.queryPairs[i];
if (key === param[0]) {
arr.push(param[1]);
}
}
return arr;
};
/**
* removes query parameters
* @param {string} key remove values for key
* @param {val} [val] remove a specific value, otherwise removes all
* @return {Uri} returns self for fluent chaining
*/
Uri.prototype.deleteQueryParam = function (key, val) {
var arr = [], i, param, keyMatchesFilter, valMatchesFilter, l;
for (i = 0, l = this.queryPairs.length; i < l; i++) {
param = this.queryPairs[i];
keyMatchesFilter = decode(param[0]) === decode(key);
valMatchesFilter = param[1] === val;
if ((arguments.length === 1 && !keyMatchesFilter) || (arguments.length === 2 && (!keyMatchesFilter || !valMatchesFilter))) {
arr.push(param);
}
}
this.queryPairs = arr;
return this;
};
/**
* adds a query parameter
* @param {string} key add values for key
* @param {string} val value to add
* @param {integer} [index] specific index to add the value at
* @return {Uri} returns self for fluent chaining
*/
Uri.prototype.addQueryParam = function (key, val, index) {
if (arguments.length === 3 && index !== -1) {
index = Math.min(index, this.queryPairs.length);
this.queryPairs.splice(index, 0, [key, val]);
} else if (arguments.length > 0) {
this.queryPairs.push([key, val]);
}
return this;
};
/**
* test for the existence of a query parameter
* @param {string} key check values for key
* @return {Boolean} true if key exists, otherwise false
*/
Uri.prototype.hasQueryParam = function (key) {
var i, len = this.queryPairs.length;
for (i = 0; i < len; i++) {
if (this.queryPairs[i][0] == key)
return true;
}
return false;
};
/**
* replaces query param values
* @param {string} key key to replace value for
* @param {string} newVal new value
* @param {string} [oldVal] replace only one specific value (otherwise replaces all)
* @return {Uri} returns self for fluent chaining
*/
Uri.prototype.replaceQueryParam = function (key, newVal, oldVal) {
var index = -1, len = this.queryPairs.length, i, param;
if (arguments.length === 3) {
for (i = 0; i < len; i++) {
param = this.queryPairs[i];
if (decode(param[0]) === decode(key) && decodeURIComponent(param[1]) === decode(oldVal)) {
index = i;
break;
}
}
if (index >= 0) {
this.deleteQueryParam(key, decode(oldVal)).addQueryParam(key, newVal, index);
}
} else {
for (i = 0; i < len; i++) {
param = this.queryPairs[i];
if (decode(param[0]) === decode(key)) {
index = i;
break;
}
}
this.deleteQueryParam(key);
this.addQueryParam(key, newVal, index);
}
return this;
};
/**
* Define fluent setter methods (setProtocol, setHasAuthorityPrefix, etc)
*/
['protocol', 'hasAuthorityPrefix', 'isColonUri', 'userInfo', 'host', 'port', 'path', 'query', 'anchor'].forEach(function(key) {
var method = 'set' + key.charAt(0).toUpperCase() + key.slice(1);
Uri.prototype[method] = function(val) {
this[key](val);
return this;
};
});
/**
* Scheme name, colon and doubleslash, as required
* @return {string} http:// or possibly just //
*/
Uri.prototype.scheme = function() {
var s = '';
if (this.protocol()) {
s += this.protocol();
if (this.protocol().indexOf(':') !== this.protocol().length - 1) {
s += ':';
}
s += '//';
} else {
if (this.hasAuthorityPrefix() && this.host()) {
s += '//';
}
}
return s;
};
/**
* Same as Mozilla nsIURI.prePath
* @return {string} scheme://user:password@host:port
* @see https://developer.mozilla.org/en/nsIURI
*/
Uri.prototype.origin = function() {
var s = this.scheme();
if (this.userInfo() && this.host()) {
s += this.userInfo();
if (this.userInfo().indexOf('@') !== this.userInfo().length - 1) {
s += '@';
}
}
if (this.host()) {
s += this.host();
if (this.port() || (this.path() && this.path().substr(0, 1).match(/[0-9]/))) {
s += ':' + this.port();
}
}
return s;
};
/**
* Adds a trailing slash to the path
*/
Uri.prototype.addTrailingSlash = function() {
var path = this.path() || '';
if (path.substr(-1) !== '/') {
this.path(path + '/');
}
return this;
};
/**
* Serializes the internal state of the Uri object
* @return {string}
*/
Uri.prototype.toString = function() {
var path, s = this.origin();
if (this.isColonUri()) {
if (this.path()) {
s += ':'+this.path();
}
} else if (this.path()) {
path = this.path();
if (!(re.ends_with_slashes.test(s) || re.starts_with_slashes.test(path))) {
s += '/';
} else {
if (s) {
s.replace(re.ends_with_slashes, '/');
}
path = path.replace(re.starts_with_slashes, '/');
}
s += path;
} else {
if (this.host() && (this.query().toString() || this.anchor())) {
s += '/';
}
}
if (this.query().toString()) {
s += this.query().toString();
}
if (this.anchor()) {
if (this.anchor().indexOf('#') !== 0) {
s += '#';
}
s += this.anchor();
}
return s;
};
/**
* Clone a Uri object
* @return {Uri} duplicate copy of the Uri
*/
Uri.prototype.clone = function() {
return new Uri(this.toString());
};
/**
* export via AMD or CommonJS, otherwise leak a global
*/
if (typeof define === 'function' && define.amd) {
define(function() {
return Uri;
});
} else if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = Uri;
} else {
global.Uri = Uri;
}
}(this));

View File

@ -0,0 +1,254 @@
var reportedPositionFailures = 0;
navigator.geolocation.getCurrentPosition = function(success, failure, options) { //override because default implementation requires GPS permission
geoposition = JSON.parse(GBjs.getCurrentPosition());
if(options && options.maximumAge && (geoposition.timestamp < Date.now() - options.maximumAge) && reportedPositionFailures <= 10 ) {
reportedPositionFailures++;
failure({ code: 2, message: "POSITION_UNAVAILABLE"});
} else {
reportedPositionFailures = 0;
success(geoposition);
}
}
if (window.Storage){
var prefix = GBjs.getAppLocalstoragePrefix();
GBjs.gbLog("redefining local storage with prefix: " + prefix);
Storage.prototype.setItem = (function(key, value) {
this.call(localStorage,prefix + key, value);
}).bind(Storage.prototype.setItem);
Storage.prototype.getItem = (function(key) {
// console.log("I am about to return " + prefix + key);
var def = null;
if(key == 'clay-settings') {
def = '{}';
}
return this.call(localStorage,prefix + key) || def;
}).bind(Storage.prototype.getItem);
}
function loadScript(url, callback) {
// Adding the script tag to the head as suggested before
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
// Then bind the event to the callback function.
// There are several events for cross browser compatibility.
script.onreadystatechange = callback;
script.onload = callback;
// Fire the loading
head.appendChild(script);
}
function getURLVariable(variable, defaultValue) {
// Find all URL parameters
var query = location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
// If the query variable parameter is found, decode it to use and return it for use
if (pair[0] === variable) {
return decodeURIComponent(pair[1]);
}
}
return defaultValue || false;
}
function showStep(desiredStep) {
var steps = document.getElementsByClassName("step");
var testStep = null;
for (var i = 0; i < steps.length; i ++) {
if (steps[i].id == desiredStep)
testStep = steps[i].id;
}
if (testStep !== null) {
for (var i = 0; i < steps.length; i ++) {
steps[i].style.display = 'none';
}
document.getElementById(desiredStep).style.display="block";
}
}
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;
var self = this;
self.events = {};
//events processing: see http://stackoverflow.com/questions/10978311/implementing-events-in-my-own-object
self.addEventListener = function(name, handler) {
if (self.events.hasOwnProperty(name))
self.events[name].push(handler);
else
self.events[name] = [handler];
}
self.removeEventListener = function(name, handler) {
if (!self.events.hasOwnProperty(name))
return;
var index = self.events[name].indexOf(handler);
if (index != -1)
self.events[name].splice(index, 1);
}
self.evaluate = function(name, args) {
if (!self.events.hasOwnProperty(name))
return;
if (!args || !args.length)
args = [];
var evs = self.events[name], l = evs.length;
for (var i = 0; i < l; i++) {
evs[i].apply(null, args);
}
}
this.actuallyOpenURL = function() {
showStep("step1compat");
window.open(self.configurationURL.toString(), "config");
}
this.actuallySendData = function() {
GBjs.sendAppMessage(self.configurationValues);
GBjs.closeActivity();
}
this.savePreset = function() {
GBjs.saveAppStoredPreset(self.configurationValues);
}
this.loadPreset = function() {
showStep("step2");
var presetElements = document.getElementsByClassName("store_presets");
for (var i = 0; i < presetElements.length; i ++) {
presetElements[i].style.display = 'none';
}
self.configurationValues = GBjs.getAppStoredPreset();
document.getElementById("jsondata").innerHTML=self.configurationValues;
}
//needs to be called like this because of original Pebble function name
this.openURL = function(url) {
if (url.lastIndexOf("http", 0) === 0) {
document.getElementById("config_url").innerHTML=url;
var UUID = GBjs.getAppUUID();
self.configurationURL = new Uri(url).addQueryParam("return_to", "gadgetbridge://"+UUID+"?config=true&json=");
} else {
//TODO: add custom return_to
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;
}
}
this.getActiveWatchInfo = function() {
return JSON.parse(GBjs.getActiveWatchInfo());
}
this.sendAppMessage = function (dict, callbackAck, callbackNack){
try {
self.configurationValues = JSON.stringify(dict);
document.getElementById("jsondata").innerHTML=self.configurationValues;
if (callbackAck != undefined) {
callbackAck();
}
}
catch (e) {
GBjs.gbLog("sendAppMessage failed");
if (callbackNack != undefined) {
callbackNack();
}
}
}
this.getAccountToken = function() {
return '';
}
this.getWatchToken = function() {
return GBjs.getWatchToken();
}
this.getTimelineToken = function() {
return '';
}
this.showSimpleNotificationOnPebble = function(title, body) {
GBjs.gbLog("app wanted to show: " + title + " body: "+ body);
}
this.showConfiguration = function() {
console.error("This watchapp doesn't support configuration");
GBjs.closeActivity();
}
this.parseReturnedPebbleJS = function() {
var str = document.getElementById('pastereturn').value;
var needle = "pebblejs://close#";
if (str.split(needle)[1] !== undefined) {
var t = new Object();
t.response = decodeURIComponent(str.split(needle)[1]);
self.evaluate('webviewclosed',[t]);
showStep("step2");
} else {
console.error("No valid configuration found in the entered string.");
}
}
}
var Pebble = new gbPebble();
var jsConfigFile = GBjs.getAppConfigurationFile();
var storedPreset = GBjs.getAppStoredPreset();
document.addEventListener('DOMContentLoaded', function(){
if (jsConfigFile != null) {
loadScript(jsConfigFile, function() {
Pebble.evaluate('ready', [{'type': "ready"}]); //callback object apparently needed by some watchfaces
if (getURLVariable('config') == 'true') {
showStep("step2");
var json_string = getURLVariable('json');
var t = new Object();
t.response = json_string;
if (json_string != '') {
Pebble.evaluate('webviewclosed',[t]);
}
} else {
if (storedPreset === undefined) {
var presetElements = document.getElementsByClassName("load_presets");
for (var i = 0; i < presetElements.length; i ++) {
presetElements[i].style.display = 'none';
}
}
Pebble.evaluate('showConfiguration');
}
});
}
}, false);

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8"?>
<XMI timestamp="2016-01-26T23:02:14" verified="false" xmi.version="1.2" xmlns:UML="http://schema.omg.org/spec/UML/1.3">
<XMI.header>
<XMI.documentation>
<XMI.exporter>umbrello uml modeller http://umbrello.kde.org</XMI.exporter>
<XMI.exporterVersion>1.6.9</XMI.exporterVersion>
<XMI.exporterEncoding>UnicodeUTF8</XMI.exporterEncoding>
</XMI.documentation>
<XMI.metamodel xmi.name="UML" href="UML.xml" xmi.version="1.3"/>
</XMI.header>
<XMI.content>
<UML:Model isAbstract="false" isRoot="false" isSpecification="false" name="UML Model" isLeaf="false" xmi.id="m1">
<UML:Namespace.ownedElement>
<UML:Stereotype isRoot="false" isAbstract="false" isSpecification="false" name="folder" isLeaf="false" namespace="m1" visibility="public" xmi.id="folder"/>
<UML:Stereotype isRoot="false" isAbstract="false" isSpecification="false" name="datatype" isLeaf="false" namespace="m1" visibility="public" xmi.id="datatype"/>
<UML:Stereotype isRoot="false" isAbstract="false" isSpecification="false" name="interface" isLeaf="false" namespace="m1" visibility="public" xmi.id="interface"/>
<UML:Model isRoot="false" isAbstract="false" isSpecification="false" name="Logical View" isLeaf="false" namespace="m1" visibility="public" xmi.id="Logical View">
<UML:Namespace.ownedElement>
<UML:Package isRoot="false" isAbstract="false" isSpecification="false" name="Datatypes" isLeaf="false" stereotype="folder" namespace="Logical View" visibility="public" xmi.id="Datatypes">
<UML:Namespace.ownedElement>
<UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="int" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="tM1MfT3dGbDn"/>
<UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="char" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="MZCr2zI6yZo6"/>
<UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="bool" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="mtFc0pEuADEp"/>
<UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="float" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="hIRvrcjDBt2B"/>
<UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="double" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="tIHrr3vdvBFv"/>
<UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="short" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="yRn8f2wKINF9"/>
<UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="long" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="xwL6qLHheXWT"/>
<UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="unsigned int" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="aS7VdKRbkCiP"/>
<UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="unsigned short" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="EejU1wcwrx2h"/>
<UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="unsigned long" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="GJMsqsMRIRuv"/>
<UML:DataType isRoot="false" isAbstract="false" isSpecification="false" name="string" isLeaf="false" stereotype="datatype" namespace="Datatypes" visibility="public" xmi.id="jLVTWBskpZFo"/>
</UML:Namespace.ownedElement>
</UML:Package>
<UML:Interface isRoot="false" isAbstract="true" isSpecification="false" name="DeviceService" isLeaf="false" stereotype="interface" namespace="Logical View" visibility="public" xmi.id="VA6qSCiBtNc5"/>
<UML:Interface isRoot="false" isAbstract="true" isSpecification="false" name="DeviceSupport" isLeaf="false" stereotype="interface" namespace="Logical View" visibility="public" xmi.id="AKxHpDCgOPhk"/>
</UML:Namespace.ownedElement>
<XMI.extension xmi.extender="umbrello">
<diagrams>
<diagram showgrid="0" snapgrid="0" backgroundcolor="#ffffff" zoom="100" type="1" linecolor="#ff0000" usefillcolor="1" griddotcolor="#d3d3d3" showops="1" showscope="1" localid="-1" showpackage="1" showpubliconly="0" canvaswidth="0" isopen="0" showatts="1" xmi.id="31Hk7F5Bq9ac" snapcsgrid="0" snapx="25" name="class diagram" showattsig="1" textcolor="#000000" showstereotype="1" showattribassocs="1" linewidth="0" fillcolor="#ffff00" documentation="" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" canvasheight="0" showopsig="1" snapy="25">
<widgets/>
<messages/>
<associations/>
</diagram>
</diagrams>
</XMI.extension>
</UML:Model>
<UML:Model isRoot="false" isAbstract="false" isSpecification="false" name="Use Case View" isLeaf="false" namespace="m1" visibility="public" xmi.id="Use Case View">
<UML:Namespace.ownedElement/>
</UML:Model>
<UML:Model isRoot="false" isAbstract="false" isSpecification="false" name="Component View" isLeaf="false" namespace="m1" visibility="public" xmi.id="Component View">
<UML:Namespace.ownedElement>
<UML:Component isRoot="false" isAbstract="false" isSpecification="false" name="Activities" executable="0" isLeaf="false" namespace="Component View" visibility="public" xmi.id="CDLTeFMNZrp5"/>
<UML:Component isRoot="false" isAbstract="false" isSpecification="false" name="DeviceCommunicationService" executable="0" isLeaf="false" namespace="Component View" visibility="public" xmi.id="72YBMP5WcQzE">
<UML:Namespace.ownedElement>
<UML:Port isRoot="false" isAbstract="false" isSpecification="false" name="pin" isLeaf="false" namespace="72YBMP5WcQzE" visibility="public" xmi.id="xhXzgXGeAGP8"/>
<UML:Port isRoot="false" isAbstract="false" isSpecification="false" name="pin2" isLeaf="false" namespace="72YBMP5WcQzE" visibility="public" xmi.id="LcpVBJq9yM8d"/>
</UML:Namespace.ownedElement>
</UML:Component>
<UML:Component isRoot="false" isAbstract="false" isSpecification="false" name="Frontend2" executable="0" isLeaf="false" namespace="Component View" visibility="public" xmi.id="zd0ny2K62qHM"/>
<UML:Artifact isRoot="false" isAbstract="false" drawas="0" isSpecification="false" name="Service" isLeaf="false" namespace="Component View" visibility="public" xmi.id="jjfTCGwVsjy4"/>
<UML:Abstraction supplier="VA6qSCiBtNc5" isSpecification="false" name="" namespace="Component View" client="72YBMP5WcQzE" visibility="public" xmi.id="3ZwKCCl82Cel"/>
<UML:Association isSpecification="false" name="invoke" namespace="Component View" visibility="public" xmi.id="ZmBbwqKcFFuU">
<UML:Association.connection>
<UML:AssociationEnd isNavigable="false" changeability="changeable" isSpecification="false" type="CDLTeFMNZrp5" name="" aggregation="none" visibility="public" xmi.id="8FlknwQC5gyi"/>
<UML:AssociationEnd isNavigable="true" changeability="changeable" isSpecification="false" type="VA6qSCiBtNc5" name="" aggregation="none" visibility="public" xmi.id="0RPtmPzL9Au6"/>
</UML:Association.connection>
</UML:Association>
<UML:Component isRoot="false" isAbstract="false" isSpecification="false" name="Concrete Device Impl." executable="0" isLeaf="false" namespace="Component View" visibility="public" xmi.id="tDF2L96KKnVj"/>
<UML:Association isSpecification="false" name="invoke" namespace="Component View" visibility="public" xmi.id="60vlquxGX0xa">
<UML:Association.connection>
<UML:AssociationEnd isNavigable="false" changeability="changeable" isSpecification="false" type="72YBMP5WcQzE" name="" aggregation="none" visibility="public" xmi.id="EElpRDBsrFht"/>
<UML:AssociationEnd isNavigable="true" changeability="changeable" isSpecification="false" type="AKxHpDCgOPhk" name="" aggregation="none" visibility="public" xmi.id="zEQUpqXtpTfd"/>
</UML:Association.connection>
</UML:Association>
<UML:Abstraction supplier="AKxHpDCgOPhk" isSpecification="false" name="" namespace="Component View" client="tDF2L96KKnVj" visibility="public" xmi.id="LcusapQo1BbT"/>
</UML:Namespace.ownedElement>
<XMI.extension xmi.extender="umbrello">
<diagrams>
<diagram showgrid="0" snapgrid="0" backgroundcolor="#ffffff" zoom="105" type="7" linecolor="#ff0000" usefillcolor="1" griddotcolor="#d3d3d3" showops="1" showscope="1" localid="-1" showpackage="1" showpubliconly="0" canvaswidth="1931" isopen="1" showatts="1" xmi.id="UpkLkm005Mtw" snapcsgrid="0" snapx="25" name="Gadgetbridge" showattsig="1" textcolor="#000000" showstereotype="1" showattribassocs="1" linewidth="0" fillcolor="#ffff00" documentation="" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" canvasheight="1133" showopsig="1" snapy="25">
<widgets>
<componentwidget textcolor="#000000" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" height="102" linewidth="0" width="165" showstereotype="1" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="0" x="-1652,09756097561" y="-881,8487804878049" localid="2hnpF0djpL2Z" usesdiagramfillcolor="0" xmi.id="CDLTeFMNZrp5" isinstance="0"/>
<componentwidget textcolor="#000000" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" height="60" linewidth="0" width="246" showstereotype="1" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="0" x="-1654" y="-645" localid="dMZABODC4z1H" usesdiagramfillcolor="0" xmi.id="72YBMP5WcQzE" isinstance="0"/>
<interfacewidget linecolor="none" usefillcolor="1" usesdiagramfillcolor="0" x="-1589,839897589076" isinstance="0" width="40" localid="bZYTIWVHJeGR" showscope="1" height="40" drawascircle="1" showpubliconly="0" showpackage="0" xmi.id="VA6qSCiBtNc5" showattributes="0" textcolor="#000000" showstereotype="1" linewidth="0" usesdiagramusefillcolor="0" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" showopsigs="601" showattsigs="601" showoperations="1" y="-716,9293791337742">
<floatingtext linecolor="none" usefillcolor="1" usesdiagramfillcolor="1" x="-91,07317073170748" isinstance="0" width="88" localid="wsTYjTmCdq05" height="19" posttext="" xmi.id="3iFu3ryeOW6f" role="700" textcolor="none" showstereotype="1" linewidth="none" text="DeviceService" usesdiagramusefillcolor="1" fillcolor="none" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" pretext="" y="10,97560975609758"/>
</interfacewidget>
<componentwidget textcolor="#000000" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" height="60" linewidth="0" width="195" showstereotype="1" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="0" x="-1374,768315059297" y="-548,2509733721189" localid="L9H7YahrGQFd" usesdiagramfillcolor="0" xmi.id="tDF2L96KKnVj" isinstance="0"/>
<interfacewidget linecolor="none" usefillcolor="1" usesdiagramfillcolor="0" x="-1320,4" isinstance="0" width="40" localid="Xn4rYXasfszD" showscope="1" height="40" drawascircle="1" showpubliconly="0" showpackage="0" xmi.id="AKxHpDCgOPhk" showattributes="0" textcolor="#000000" showstereotype="1" linewidth="0" usesdiagramusefillcolor="0" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" showopsigs="601" showattsigs="601" showoperations="1" y="-640,1853658536585">
<floatingtext linecolor="none" usefillcolor="1" usesdiagramfillcolor="1" x="-6,602439024390151" isinstance="0" width="91" localid="Qe3g5GJDaFUO" height="19" posttext="" xmi.id="Qe4p2lZZXbQ3" role="700" textcolor="none" showstereotype="1" linewidth="none" text="DeviceSupport" usesdiagramusefillcolor="1" fillcolor="none" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" pretext="" y="-15,0634146341464"/>
</interfacewidget>
</widgets>
<messages/>
<associations>
<assocwidget totalcountb="2" textcolor="none" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" widgetaid="72YBMP5WcQzE" linewidth="none" totalcounta="2" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="94" type="511" indexb="1" seqnum="" usesdiagramfillcolor="0" widgetbid="VA6qSCiBtNc5" xmi.id="3ZwKCCl82Cel" indexa="1">
<linepath layout="Polyline">
<startpoint startx="-1569,718796671645" starty="-645"/>
<endpoint endx="-1569,718796671645" endy="-676,9293791337742"/>
</linepath>
</assocwidget>
<assocwidget totalcountb="2" textcolor="none" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" widgetaid="CDLTeFMNZrp5" linewidth="none" totalcounta="2" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="96" type="512" indexb="1" seqnum="" usesdiagramfillcolor="0" widgetbid="VA6qSCiBtNc5" xmi.id="ZmBbwqKcFFuU" indexa="1">
<linepath layout="Polyline">
<startpoint startx="-1571,551409184105" starty="-779,8487804878049"/>
<endpoint endx="-1571,551409184105" endy="-716,9293791337742"/>
</linepath>
<floatingtext linecolor="none" usefillcolor="1" usesdiagramfillcolor="1" x="-1567,112384793861" isinstance="0" width="44" localid="yCgBPagYnPIa" height="19" posttext="" xmi.id="LilOMj6M9VGv" role="703" textcolor="none" showstereotype="1" linewidth="none" text="invoke" usesdiagramusefillcolor="1" fillcolor="none" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" pretext="" y="-758,1110310303017"/>
</assocwidget>
<assocwidget totalcountb="2" textcolor="none" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" widgetaid="72YBMP5WcQzE" linewidth="none" totalcounta="2" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="2" type="512" indexb="1" seqnum="" usesdiagramfillcolor="0" widgetbid="AKxHpDCgOPhk" xmi.id="60vlquxGX0xa" indexa="1">
<linepath layout="Polyline">
<startpoint startx="-1408" starty="-617,0146341463413"/>
<endpoint endx="-1320,4" endy="-617,0146341463413"/>
</linepath>
<floatingtext linecolor="none" usefillcolor="1" usesdiagramfillcolor="1" x="-1382,614634146342" isinstance="0" width="44" localid="S2UTD5uasDos" height="19" posttext="" xmi.id="BrpNNGMiEFrg" role="703" textcolor="none" showstereotype="1" linewidth="none" text="invoke" usesdiagramusefillcolor="1" fillcolor="none" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" pretext="" y="-613,3439024390243"/>
</assocwidget>
<assocwidget totalcountb="2" textcolor="none" fillcolor="#ffff00" font="Liberation Sans,10,-1,5,50,0,0,0,0,0" widgetaid="tDF2L96KKnVj" linewidth="none" totalcounta="2" linecolor="none" usefillcolor="1" usesdiagramusefillcolor="53" type="511" indexb="1" seqnum="" usesdiagramfillcolor="0" widgetbid="AKxHpDCgOPhk" xmi.id="LcusapQo1BbT" indexa="1">
<linepath layout="Polyline">
<startpoint startx="-1299,912195121952" starty="-548,2509733721189"/>
<endpoint endx="-1299,912195121952" endy="-600,1853658536585"/>
</linepath>
</assocwidget>
</associations>
</diagram>
</diagrams>
</XMI.extension>
</UML:Model>
<UML:Model isRoot="false" isAbstract="false" isSpecification="false" name="Deployment View" isLeaf="false" namespace="m1" visibility="public" xmi.id="Deployment View">
<UML:Namespace.ownedElement/>
</UML:Model>
<UML:Model isRoot="false" isAbstract="false" isSpecification="false" name="Entity Relationship Model" isLeaf="false" namespace="m1" visibility="public" xmi.id="Entity Relationship Model">
<UML:Namespace.ownedElement/>
</UML:Model>
</UML:Namespace.ownedElement>
</UML:Model>
</XMI.content>
<XMI.extensions xmi.extender="umbrello">
<docsettings viewid="UpkLkm005Mtw" uniqueid="BrpNNGMiEFrg" documentation=""/>
<listview>
<listitem type="800" id="Views" open="1">
<listitem type="821" id="Component View" open="1">
<listitem type="822" id="CDLTeFMNZrp5" open="1"/>
<listitem type="822" id="tDF2L96KKnVj" open="1"/>
<listitem type="822" id="72YBMP5WcQzE" open="1">
<listitem type="845" id="xhXzgXGeAGP8" open="1"/>
<listitem type="845" id="LcpVBJq9yM8d" open="1"/>
</listitem>
<listitem type="822" id="zd0ny2K62qHM" open="1"/>
<listitem type="819" label="Gadgetbridge" id="UpkLkm005Mtw" open="0"/>
<listitem type="824" id="jjfTCGwVsjy4" open="1"/>
</listitem>
<listitem type="827" id="Deployment View" open="1"/>
<listitem type="836" id="Entity Relationship Model" open="1"/>
<listitem type="801" id="Logical View" open="1">
<listitem type="807" label="class diagram" id="31Hk7F5Bq9ac" open="0"/>
<listitem type="830" id="Datatypes" open="0">
<listitem type="829" id="mtFc0pEuADEp" open="1"/>
<listitem type="829" id="MZCr2zI6yZo6" open="1"/>
<listitem type="829" id="tIHrr3vdvBFv" open="1"/>
<listitem type="829" id="hIRvrcjDBt2B" open="1"/>
<listitem type="829" id="tM1MfT3dGbDn" open="1"/>
<listitem type="829" id="xwL6qLHheXWT" open="1"/>
<listitem type="829" id="yRn8f2wKINF9" open="1"/>
<listitem type="829" id="jLVTWBskpZFo" open="1"/>
<listitem type="829" id="aS7VdKRbkCiP" open="1"/>
<listitem type="829" id="GJMsqsMRIRuv" open="1"/>
<listitem type="829" id="EejU1wcwrx2h" open="1"/>
</listitem>
<listitem type="817" id="VA6qSCiBtNc5" open="1"/>
<listitem type="817" id="AKxHpDCgOPhk" open="1"/>
</listitem>
<listitem type="802" id="Use Case View" open="1"/>
</listitem>
</listview>
<codegeneration>
<codegenerator language="C++"/>
</codegeneration>
</XMI.extensions>
</XMI>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -15,15 +15,19 @@
<!-- encoders are by default assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${GB_LOGFILES_DIR}/gadgetbridge-%d{yyyy-MM-dd}.log.zip</fileNamePattern>
<fileNamePattern>${GB_LOGFILES_DIR}/gadgetbridge-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern>
<maxHistory>10</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 50MB -->
<maxFileSize>2MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>5MB</maxFileSize>
</triggeringPolicy>
<encoder>
<!--<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>-->
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{1} - %msg%n</pattern>
<!-- to debug crashes, set immediateFlush to true, otherwise keep it false to improve throughput -->
<immediateFlush>false</immediateFlush>
<!--<pattern>%date [%thread] %-5level %logger{25} - %msg%n</pattern>-->
</encoder>
</appender>

View File

@ -1,72 +1,208 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Normano64
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;
import android.annotation.TargetApi;
import android.app.Application;
import android.app.NotificationManager;
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;
import android.net.Uri;
import android.os.Build;
import android.os.Build.VERSION;
import android.preference.PreferenceManager;
import android.provider.ContactsContract.PhoneLookup;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.util.TypedValue;
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;
import java.util.concurrent.locks.ReentrantLock;
import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.service.NotificationCollectorMonitorService;
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
/**
* Main Application class that initializes and provides access to certain things like
* logging and DB access.
*/
public class GBApplication extends Application {
// Since this class must not log to slf4j, we use plain android.util.Log
private static final String TAG = "GBApplication";
public static final String DATABASE_NAME = "Gadgetbridge";
private static GBApplication context;
private static ActivityDatabaseHandler mActivityDatabaseHandler;
private static final Lock dbLock = new ReentrantLock();
private static DeviceService deviceService;
private static SharedPreferences sharedPrefs;
private static final String PREFS_VERSION = "shared_preferences_version";
//if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version
private static final int CURRENT_PREFS_VERSION = 2;
private static LimitedQueue mIDSenderLookup = new LimitedQueue(16);
private static Prefs prefs;
private static GBPrefs gbPrefs;
private static LockHandler lockHandler;
/**
* Note: is null on Lollipop and Kitkat
*/
private static NotificationManager notificationManager;
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;
private static Logging logging = new Logging() {
@Override
protected String createLogDirectory() throws IOException {
if (GBEnvironment.env().isLocalTest()) {
return System.getProperty(Logging.PROP_LOGFILES_DIR);
} else {
File dir = FileUtils.getExternalFilesDir();
return dir.getAbsolutePath();
}
}
};
private static Locale language;
private DeviceManager deviceManager;
public static void quit() {
GB.log("Quitting Gadgetbridge...", GB.INFO, null);
Intent quitIntent = new Intent(GBApplication.ACTION_QUIT);
LocalBroadcastManager.getInstance(context).sendBroadcast(quitIntent);
GBApplication.deviceService().quit();
}
public GBApplication() {
context = this;
// don't do anything here, add it to onCreate instead
}
public static Logging getLogging() {
return logging;
}
protected DeviceService createDeviceService() {
return new GBDeviceService(this);
}
@Override
public void onCreate() {
app = this;
super.onCreate();
if (lockHandler != null) {
// guard against multiple invocations (robolectric)
return;
}
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs = new Prefs(sharedPrefs);
gbPrefs = new GBPrefs(prefs);
if (!GBEnvironment.isEnvironmentSetup()) {
GBEnvironment.setupEnvironment(GBEnvironment.createDeviceEnvironment());
// setup db after the environment is set up, but don't do it in test mode
// in test mode, it's done individually, see TestBase
setupDatabase();
}
// don't do anything here before we set up logging, otherwise
// slf4j may be implicitly initialized before we properly configured it.
setupLogging();
setupLogging(isFileLoggingEnabled());
if (getPrefsFileVersion() != CURRENT_PREFS_VERSION) {
migratePrefs(getPrefsFileVersion());
}
setupExceptionHandler();
// For debugging problems with the logback configuration
// LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// print logback's internal status
// StatusPrinter.print(lc);
// Logger logger = LoggerFactory.getLogger(GBApplication.class);
deviceManager = new DeviceManager(this);
String language = prefs.getString("language", "default");
setLanguage(language);
deviceService = createDeviceService();
GB.environment = GBEnvironment.createDeviceEnvironment();
mActivityDatabaseHandler = new ActivityDatabaseHandler(context);
loadBlackList();
// for testing DB stuff
// SQLiteDatabase db = mActivityDatabaseHandler.getWritableDatabase();
// db.close();
loadAppsBlackList();
loadCalendarsBlackList();
if (isRunningMarshmallowOrLater()) {
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
//the following will ensure the notification manager is kept alive
startService(new Intent(this, NotificationCollectorMonitorService.class));
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= TRIM_MEMORY_BACKGROUND) {
if (!hasBusyDevice()) {
DBHelper.clearSession();
}
}
}
/**
* Returns true if at least a single device is busy, e.g synchronizing activity data
* or something similar.
* Note: busy is not the same as connected or initialized!
*/
private boolean hasBusyDevice() {
List<GBDevice> devices = getDeviceManager().getDevices();
for (GBDevice device : devices) {
if (device.isBusy()) {
return true;
}
}
return false;
}
public static void setupLogging(boolean enabled) {
logging.setupLogging(enabled);
}
private void setupExceptionHandler() {
@ -75,42 +211,39 @@ public class GBApplication extends Application {
}
public static boolean isFileLoggingEnabled() {
return sharedPrefs.getBoolean("log_to_file", false);
return prefs.getBoolean("log_to_file", false);
}
private void setupLogging() {
if (isFileLoggingEnabled()) {
try {
File dir = FileUtils.getExternalFilesDir();
// used by assets/logback.xml since the location cannot be statically determined
System.setProperty("GB_LOGFILES_DIR", dir.getAbsolutePath());
getLogger().info("Gadgetbridge version: " + BuildConfig.VERSION_NAME);
} catch (IOException ex) {
Log.e("GBApplication", "External files dir not available, cannot log to file", ex);
removeFileLogger();
}
public static boolean minimizeNotification() {
return prefs.getBoolean("minimize_priority", false);
}
public void setupDatabase() {
DaoMaster.OpenHelper helper;
GBEnvironment env = GBEnvironment.env();
if (env.isTest()) {
helper = new DaoMaster.DevOpenHelper(this, null, null);
} else {
removeFileLogger();
helper = new DBOpenHelper(this, DATABASE_NAME, null);
}
}
private void removeFileLogger() {
try {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
root.detachAppender("FILE");
} catch (Throwable ex) {
Log.e("GBApplication", "Error removing logger FILE appender", ex);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
if (lockHandler == null) {
lockHandler = new LockHandler();
}
}
private Logger getLogger() {
return LoggerFactory.getLogger(GBApplication.class);
lockHandler.init(daoMaster, helper);
}
public static Context getContext() {
return context;
}
/**
* Returns the facade for talking to devices. Devices are managed by
* an Android Service and this facade provides access to its functionality.
*
* @return the facade for talking to the service/devices.
*/
public static DeviceService deviceService() {
return deviceService;
}
@ -120,6 +253,9 @@ public class GBApplication extends Application {
* when that was not successful
* If acquiring was successful, callers must call #releaseDB when they
* are done (from the same thread that acquired the lock!
* <p>
* Callers must not hold a reference to the returned instance because it
* will be invalidated at some point.
*
* @return the DBHandler
* @throws GBException
@ -128,7 +264,7 @@ public class GBApplication extends Application {
public static DBHandler acquireDB() throws GBException {
try {
if (dbLock.tryLock(30, TimeUnit.SECONDS)) {
return mActivityDatabaseHandler;
return lockHandler;
}
} catch (InterruptedException ex) {
Log.i(TAG, "Interrupted while waiting for DB lock");
@ -150,35 +286,309 @@ public class GBApplication extends Application {
return VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
public static HashSet<String> blacklist = null;
public static boolean isRunningMarshmallowOrLater() {
return VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
public static void loadBlackList() {
blacklist = (HashSet<String>) sharedPrefs.getStringSet("package_blacklist", null);
if (blacklist == null) {
blacklist = new HashSet<>();
private static boolean isPrioritySender(int prioritySenders, String number) {
if (prioritySenders == Policy.PRIORITY_SENDERS_ANY) {
return true;
} else {
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
String[] projection = new String[]{PhoneLookup._ID, PhoneLookup.STARRED};
Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
boolean exists = false;
int starred = 0;
try {
if (cursor != null && cursor.moveToFirst()) {
exists = true;
starred = cursor.getInt(cursor.getColumnIndexOrThrow(PhoneLookup.STARRED));
}
} finally {
if (cursor != null) {
cursor.close();
}
}
if (prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS && exists) {
return true;
} else if (prioritySenders == Policy.PRIORITY_SENDERS_STARRED && starred == 1) {
return true;
}
return false;
}
}
public static void saveBlackList() {
SharedPreferences.Editor editor = sharedPrefs.edit();
if (blacklist.isEmpty()) {
editor.putStringSet("package_blacklist", null);
@TargetApi(Build.VERSION_CODES.M)
public static boolean isPriorityNumber(int priorityType, String number) {
NotificationManager.Policy notificationPolicy = notificationManager.getNotificationPolicy();
if (priorityType == Policy.PRIORITY_CATEGORY_MESSAGES) {
if ((notificationPolicy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) == Policy.PRIORITY_CATEGORY_MESSAGES) {
return isPrioritySender(notificationPolicy.priorityMessageSenders, number);
}
} else if (priorityType == Policy.PRIORITY_CATEGORY_CALLS) {
if ((notificationPolicy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) == Policy.PRIORITY_CATEGORY_CALLS) {
return isPrioritySender(notificationPolicy.priorityCallSenders, number);
}
}
return false;
}
@TargetApi(Build.VERSION_CODES.M)
public static int getGrantedInterruptionFilter() {
if (prefs.getBoolean("notification_filter", false) && GBApplication.isRunningMarshmallowOrLater()) {
if (notificationManager.isNotificationPolicyAccessGranted()) {
return notificationManager.getCurrentInterruptionFilter();
}
}
return NotificationManager.INTERRUPTION_FILTER_ALL;
}
private static HashSet<String> apps_blacklist = null;
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 setAppsBlackList(Set<String> packageNames) {
if (packageNames == null) {
GB.log("Set null apps_blacklist", GB.INFO, null);
apps_blacklist = new HashSet<>();
} else {
editor.putStringSet("package_blacklist", blacklist);
apps_blacklist = new HashSet<>(packageNames);
}
GB.log("New apps_blacklist has " + apps_blacklist.size() + " entries", GB.INFO, null);
saveAppsBlackList();
}
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 saveAppsBlackList() {
GB.log("Saving apps_blacklist with " + apps_blacklist.size() + " entries", GB.INFO, null);
SharedPreferences.Editor editor = sharedPrefs.edit();
if (apps_blacklist.isEmpty()) {
editor.putStringSet(GBPrefs.PACKAGE_BLACKLIST, null);
} else {
Prefs.putStringSet(editor, GBPrefs.PACKAGE_BLACKLIST, apps_blacklist);
}
editor.apply();
}
public static void addToBlacklist(String packageName) {
if (!blacklist.contains(packageName)) {
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();
}
/**
* Deletes both the old Activity database and the new one recreates it with empty tables.
*
* @return true on successful deletion
*/
public static synchronized boolean deleteActivityDatabase(Context context) {
// TODO: flush, close, reopen db
if (lockHandler != null) {
lockHandler.closeDb();
}
boolean result = deleteOldActivityDatabase(context);
result &= getContext().deleteDatabase(DATABASE_NAME);
return result;
}
/**
* Deletes the legacy (pre 0.12) Activity database
*
* @return true on successful deletion
*/
public static synchronized boolean deleteOldActivityDatabase(Context context) {
DBHelper dbHelper = new DBHelper(context);
boolean result = true;
if (dbHelper.existsDB("ActivityDatabase")) {
result = getContext().deleteDatabase("ActivityDatabase");
}
return result;
}
private int getPrefsFileVersion() {
try {
return Integer.parseInt(sharedPrefs.getString(PREFS_VERSION, "0")); //0 is legacy
} catch (Exception e) {
//in version 1 this was an int
return 1;
}
}
private void migratePrefs(int oldVersion) {
SharedPreferences.Editor editor = sharedPrefs.edit();
switch (oldVersion) {
case 0:
String legacyGender = sharedPrefs.getString("mi_user_gender", null);
String legacyHeight = sharedPrefs.getString("mi_user_height_cm", null);
String legacyWeigth = sharedPrefs.getString("mi_user_weight_kg", null);
String legacyYOB = sharedPrefs.getString("mi_user_year_of_birth", null);
if (legacyGender != null) {
int gender = "male".equals(legacyGender) ? 1 : "female".equals(legacyGender) ? 0 : 2;
editor.putString(ActivityUser.PREF_USER_GENDER, Integer.toString(gender));
editor.remove("mi_user_gender");
}
if (legacyHeight != null) {
editor.putString(ActivityUser.PREF_USER_HEIGHT_CM, legacyHeight);
editor.remove("mi_user_height_cm");
}
if (legacyWeigth != null) {
editor.putString(ActivityUser.PREF_USER_WEIGHT_KG, legacyWeigth);
editor.remove("mi_user_weight_kg");
}
if (legacyYOB != null) {
editor.putString(ActivityUser.PREF_USER_YEAR_OF_BIRTH, legacyYOB);
editor.remove("mi_user_year_of_birth");
}
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
break;
case 1:
//migrate the integer version of gender introduced in version 1 to a string value, needed for the way Android accesses the shared preferences
int legacyGender_1 = 2;
try {
legacyGender_1 = sharedPrefs.getInt(ActivityUser.PREF_USER_GENDER, 2);
} catch (Exception e) {
Log.e(TAG, "Could not access legacy activity gender", e);
}
editor.putString(ActivityUser.PREF_USER_GENDER, Integer.toString(legacyGender_1));
//also silently migrate the version to a string value
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
break;
}
editor.apply();
}
public static void setLanguage(String lang) {
if (lang.equals("default")) {
language = Resources.getSystem().getConfiguration().locale;
} else {
language = new Locale(lang);
}
updateLanguage(language);
}
public static void updateLanguage(Locale locale) {
AndroidUtils.setLanguage(context, locale);
Intent intent = new Intent();
intent.setAction(ACTION_LANGUAGE_CHANGE);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
public static LimitedQueue getIDSenderLookup() {
return mIDSenderLookup;
}
public static boolean isDarkThemeEnabled() {
return prefs.getString("pref_key_theme", context.getString(R.string.pref_theme_value_light)).equals(context.getString(R.string.pref_theme_value_dark));
}
public static int getTextColor(Context context) {
TypedValue typedValue = new TypedValue();
Resources.Theme theme = context.getTheme();
theme.resolveAttribute(R.attr.textColorPrimary, typedValue, true);
return typedValue.data;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateLanguage(getLanguage());
}
public static int getBackgroundColor(Context context) {
TypedValue typedValue = new TypedValue();
Resources.Theme theme = context.getTheme();
theme.resolveAttribute(android.R.attr.background, typedValue, true);
return typedValue.data;
}
public static Prefs getPrefs() {
return prefs;
}
public static GBPrefs getGBPrefs() {
return gbPrefs;
}
public DeviceManager getDeviceManager() {
return deviceManager;
}
public static GBApplication app() {
return app;
}
public static Locale getLanguage() {
return language;
}
}

View File

@ -1,6 +1,29 @@
/* 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;
/**
* Some more or less useful utility methods to aid local (non-device) testing.
*/
public class GBEnvironment {
// DO NOT USE A LOGGER HERE. Will break LoggingTest!
// private static final Logger LOG = LoggerFactory.getLogger(GBEnvironment.class);
private static GBEnvironment environment;
private boolean localTest;
private boolean deviceTest;
@ -10,9 +33,8 @@ public class GBEnvironment {
return env;
}
public static GBEnvironment createDeviceEnvironment() {
GBEnvironment env = new GBEnvironment();
return env;
static GBEnvironment createDeviceEnvironment() {
return new GBEnvironment();
}
public final boolean isTest() {
@ -23,4 +45,15 @@ public class GBEnvironment {
return localTest;
}
public static synchronized GBEnvironment env() {
return environment;
}
static synchronized boolean isEnvironmentSetup() {
return environment != null;
}
public synchronized static void setupEnvironment(GBEnvironment env) {
environment = env;
}
}

View File

@ -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/>. */
package nodomain.freeyourgadget.gadgetbridge;
public class GBException extends Exception {

View File

@ -0,0 +1,116 @@
/* 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;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
/**
* Provides lowlevel access to the database.
*/
public class LockHandler implements DBHandler {
private DaoMaster daoMaster = null;
private DaoSession session = null;
private SQLiteOpenHelper helper = null;
public LockHandler() {
}
public void init(DaoMaster daoMaster, DaoMaster.OpenHelper helper) {
if (isValid()) {
throw new IllegalStateException("DB must be closed before initializing it again");
}
if (daoMaster == null) {
throw new IllegalArgumentException("daoMaster must not be null");
}
if (helper == null) {
throw new IllegalArgumentException("helper must not be null");
}
this.daoMaster = daoMaster;
this.helper = helper;
session = daoMaster.newSession();
if (session == null) {
throw new RuntimeException("Unable to create database session");
}
}
@Override
public DaoMaster getDaoMaster() {
return daoMaster;
}
private boolean isValid() {
return daoMaster != null;
}
private void ensureValid() {
if (!isValid()) {
throw new IllegalStateException("LockHandler is not in a valid state");
}
}
@Override
public void close() {
ensureValid();
GBApplication.releaseDB();
}
@Override
public synchronized void openDb() {
if (session != null) {
throw new IllegalStateException("session must be null");
}
// this will create completely new db instances and in turn update this handler through #init()
GBApplication.app().setupDatabase();
}
@Override
public synchronized void closeDb() {
if (session == null) {
throw new IllegalStateException("session must not be null");
}
session.clear();
session.getDatabase().close();
session = null;
helper = null;
daoMaster = null;
}
@Override
public SQLiteOpenHelper getHelper() {
ensureValid();
return helper;
}
@Override
public DaoSession getDaoSession() {
ensureValid();
return session;
}
@Override
public SQLiteDatabase getDatabase() {
ensureValid();
return daoMaster.getDatabase();
}
}

View File

@ -0,0 +1,164 @@
/* Copyright (C) 2016-2017 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;
import android.util.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.util.StatusPrinter;
public abstract class Logging {
public static final String PROP_LOGFILES_DIR = "GB_LOGFILES_DIR";
private FileAppender<ILoggingEvent> fileLogger;
public void setupLogging(boolean enable) {
try {
if (fileLogger == null) {
init();
}
if (enable) {
startFileLogger();
} else {
stopFileLogger();
}
getLogger().info("Gadgetbridge version: " + BuildConfig.VERSION_NAME);
} catch (IOException ex) {
Log.e("GBApplication", "External files dir not available, cannot log to file", ex);
stopFileLogger();
}
}
public void debugLoggingConfiguration() {
// For debugging problems with the logback configuration
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// print logback's internal status
StatusPrinter.print(lc);
// Logger logger = LoggerFactory.getLogger(Logging.class);
}
protected abstract String createLogDirectory() throws IOException;
protected void init() throws IOException {
String dir = createLogDirectory();
if (dir == null) {
throw new IllegalArgumentException("log directory must not be null");
}
// used by assets/logback.xml since the location cannot be statically determined
System.setProperty(PROP_LOGFILES_DIR, dir);
rememberFileLogger();
}
private Logger getLogger() {
return LoggerFactory.getLogger(Logging.class);
}
private void startFileLogger() {
if (fileLogger != null && !fileLogger.isStarted()) {
addFileLogger(fileLogger);
fileLogger.setLazy(false); // hack to make sure that start() actually opens the file
fileLogger.start();
}
}
private void stopFileLogger() {
if (fileLogger != null && fileLogger.isStarted()) {
fileLogger.stop();
removeFileLogger(fileLogger);
}
}
private void rememberFileLogger() {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
fileLogger = (FileAppender<ILoggingEvent>) root.getAppender("FILE");
}
private void addFileLogger(Appender<ILoggingEvent> fileLogger) {
try {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
if (!root.isAttached(fileLogger)) {
root.addAppender(fileLogger);
}
} catch (Throwable ex) {
Log.e("GBApplication", "Error adding logger FILE appender", ex);
}
}
private void removeFileLogger(Appender<ILoggingEvent> fileLogger) {
try {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
if (root.isAttached(fileLogger)) {
root.detachAppender(fileLogger);
}
} catch (Throwable ex) {
Log.e("GBApplication", "Error removing logger FILE appender", ex);
}
}
public FileAppender<ILoggingEvent> getFileLogger() {
return fileLogger;
}
public boolean setImmediateFlush(boolean enable) {
FileAppender<ILoggingEvent> fileLogger = getFileLogger();
Encoder<ILoggingEvent> encoder = fileLogger.getEncoder();
if (encoder instanceof LayoutWrappingEncoder) {
((LayoutWrappingEncoder) encoder).setImmediateFlush(enable);
return true;
}
return false;
}
public boolean isImmediateFlush() {
FileAppender<ILoggingEvent> fileLogger = getFileLogger();
Encoder<ILoggingEvent> encoder = fileLogger.getEncoder();
if (encoder instanceof LayoutWrappingEncoder) {
return ((LayoutWrappingEncoder) encoder).isImmediateFlush();
}
return false;
}
public static String formatBytes(byte[] bytes) {
if (bytes == null) {
return "(null)";
}
StringBuilder builder = new StringBuilder(bytes.length * 5);
for (byte b : bytes) {
builder.append(String.format("0x%2x", b));
builder.append(" ");
}
return builder.toString().trim();
}
public static void logBytes(Logger logger, byte[] value) {
if (value != null) {
for (byte b : value) {
logger.warn("DATA: " + String.format("0x%2x", b));
}
}
}
}

View File

@ -1,9 +1,28 @@
/* Copyright (C) 2015-2017 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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Catches otherwise uncaught exceptions, logs them and terminates the app.
*/
public class LoggingExceptionHandler implements Thread.UncaughtExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(LoggingExceptionHandler.class);
private final Thread.UncaughtExceptionHandler mDelegate;

View File

@ -0,0 +1,131 @@
/* Copyright (C) 2016-2017 0nse, 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;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.widget.RemoteViews;
import android.widget.Toast;
import java.util.Calendar;
import java.util.GregorianCalendar;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
/**
* Implementation of SleepAlarmWidget functionality. When pressing the widget, an alarm will be set
* to trigger after a predefined number of hours. A toast will confirm the user about this. The
* value is retrieved using ActivityUser.().getSleepDuration().
*/
public class SleepAlarmWidget extends AppWidgetProvider {
/**
* This is our dedicated action to detect when the widget has been clicked.
*/
public static final String ACTION =
"nodomain.freeyourgadget.gadgetbridge.SLEEP_ALARM_WIDGET_CLICK";
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
// Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.sleep_alarm_widget);
// Add our own click intent
Intent intent = new Intent(ACTION);
PendingIntent clickPI = PendingIntent.getBroadcast(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.sleepalarmwidget_text, clickPI);
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// There may be multiple widgets active, so update all of them
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
}
@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (ACTION.equals(intent.getAction())) {
int userSleepDuration = new ActivityUser().getSleepDuration();
// current timestamp
GregorianCalendar calendar = new GregorianCalendar();
// add preferred sleep duration
calendar.add(Calendar.HOUR_OF_DAY, userSleepDuration);
// overwrite the first alarm and activate it
GBAlarm alarm = GBAlarm.createSingleShot(0, true, calendar);
alarm.store();
if (GBApplication.isRunningLollipopOrLater()) {
setAlarmViaAlarmManager(context, calendar.getTimeInMillis());
}
int hours = calendar.get(Calendar.HOUR_OF_DAY);
int minutes = calendar.get(Calendar.MINUTE);
GB.toast(context,
String.format(context.getString(R.string.appwidget_alarms_set), hours, minutes),
Toast.LENGTH_SHORT, GB.INFO);
}
}
/**
* Use the Android alarm manager to create the alarm icon in the status bar.
*
* @param packageContext {@code Context}: A Context of the application package implementing this
* class.
* @param triggerTime {@code long}: time at which the underlying alarm is triggered in wall time
* milliseconds since the epoch
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setAlarmViaAlarmManager(Context packageContext, long triggerTime) {
AlarmManager am = (AlarmManager) packageContext.getSystemService(Context.ALARM_SERVICE);
// TODO: launch the alarm configuration activity when clicking the alarm in the status bar
Intent intent = new Intent(packageContext, ConfigureAlarms.class);
PendingIntent pi = PendingIntent.getBroadcast(packageContext, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
am.setAlarmClock(new AlarmManager.AlarmClockInfo(triggerTime, pi), pi);
}
}

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2015-2017 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.activities;
import android.support.v4.app.FragmentManager;
@ -8,7 +24,7 @@ import java.util.HashSet;
import java.util.Set;
public abstract class AbstractFragmentPagerAdapter extends FragmentStatePagerAdapter {
private Set<AbstractGBFragment> fragments = new HashSet<>();
private final Set<AbstractGBFragment> fragments = new HashSet<>();
private Object primaryFragment;
public AbstractFragmentPagerAdapter(FragmentManager fm) {

View File

@ -0,0 +1,109 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.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.support.v7.app.AppCompatActivity;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
public abstract class AbstractGBActivity extends AppCompatActivity implements GBActivity {
private boolean isLanguageInvalid = false;
public static final int NONE = 0;
public static final int NO_ACTIONBAR = 1;
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(), true);
break;
case GBApplication.ACTION_QUIT:
finish();
break;
}
}
};
public void setLanguage(Locale language, boolean invalidateLanguage) {
if (invalidateLanguage) {
isLanguageInvalid = true;
}
AndroidUtils.setLanguage(this, language);
}
public static void init(GBActivity activity) {
init(activity, NONE);
}
public static void init(GBActivity activity, int flags) {
if (GBApplication.isDarkThemeEnabled()) {
if ((flags & NO_ACTIONBAR) != 0) {
activity.setTheme(R.style.GadgetbridgeThemeDark_NoActionBar);
} else {
activity.setTheme(R.style.GadgetbridgeThemeDark);
}
} else {
if ((flags & NO_ACTIONBAR) != 0) {
activity.setTheme(R.style.GadgetbridgeTheme_NoActionBar);
} else {
activity.setTheme(R.style.GadgetbridgeTheme);
}
}
activity.setLanguage(GBApplication.getLanguage(), false);
}
@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);
init(this);
super.onCreate(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
if (isLanguageInvalid) {
isLanguageInvalid = false;
recreate();
}
}
@Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
super.onDestroy();
}
}

View File

@ -1,8 +1,24 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, walkjivefly
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.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
/**
* Abstract base class for fragments. Provides hooks that are called when
@ -12,7 +28,7 @@ import android.support.v4.app.FragmentActivity;
* @see AbstractGBFragmentActivity
*/
public abstract class AbstractGBFragment extends Fragment {
private boolean mVisibleInactivity;
private boolean mVisibleInActivity;
/**
* Called when this fragment has been fully scrolled into the activity.
@ -21,7 +37,6 @@ public abstract class AbstractGBFragment extends Fragment {
* @see #onMadeInvisibleInActivity()
*/
protected void onMadeVisibleInActivity() {
updateActivityTitle();
}
/**
@ -31,7 +46,7 @@ public abstract class AbstractGBFragment extends Fragment {
* @see #onMadeVisibleInActivity()
*/
protected void onMadeInvisibleInActivity() {
mVisibleInactivity = false;
mVisibleInActivity = false;
}
/**
@ -39,16 +54,7 @@ public abstract class AbstractGBFragment extends Fragment {
* activity, not taking into account whether the screen is enabled at all.
*/
public boolean isVisibleInActivity() {
return mVisibleInactivity;
}
protected void updateActivityTitle() {
FragmentActivity activity = (FragmentActivity) getActivity();
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
if (getTitle() != null) {
activity.setTitle(getTitle());
}
}
return mVisibleInActivity;
}
@Nullable
@ -60,7 +66,7 @@ public abstract class AbstractGBFragment extends Fragment {
* @hide
*/
public void onMadeVisibleInActivityInternal() {
mVisibleInactivity = true;
mVisibleInActivity = true;
if (isVisible()) {
onMadeVisibleInActivity();
}

View File

@ -1,7 +1,22 @@
/* 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.activities;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
@ -18,7 +33,7 @@ import android.support.v4.app.FragmentPagerAdapter;
*
* @see AbstractGBFragment
*/
public abstract class AbstractGBFragmentActivity extends FragmentActivity {
public abstract class AbstractGBFragmentActivity extends AbstractGBActivity {
/**
* The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a

View File

@ -1,26 +1,72 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Christian
Fischer, Daniele Gobbetti, Lem Dulfo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.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.PreferenceActivity;
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.util.AndroidUtils;
/**
* A settings activity with support for preferences directly displaying their value.
* If you combine such preferences with a custom OnPreferenceChangeListener, you have
* to set that listener in #onCreate, *not* in #onPostCreate, otherwise the value will
* not be displayed.
*/
public class AbstractSettingsActivity extends PreferenceActivity {
public abstract class AbstractSettingsActivity extends AppCompatPreferenceActivity implements GBActivity {
private static final Logger LOG = LoggerFactory.getLogger(AbstractSettingsActivity.class);
private boolean isLanguageInvalid = false;
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(), true);
break;
case GBApplication.ACTION_QUIT:
finish();
break;
}
}
};
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
@ -28,12 +74,20 @@ public class AbstractSettingsActivity extends PreferenceActivity {
private static class SimpleSetSummaryOnChangeListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
if (preference instanceof EditTextPreference) {
if ((((EditTextPreference) preference).getEditText().getKeyListener().getInputType() & InputType.TYPE_CLASS_NUMBER) != 0) {
if ("".equals(String.valueOf(value))) {
// reject empty numeric input
return false;
}
}
}
updateSummary(preference, value);
return true;
}
public void updateSummary(Preference preference, Object value) {
String stringValue = value.toString();
String stringValue = String.valueOf(value);
if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in
@ -56,15 +110,15 @@ public class AbstractSettingsActivity extends PreferenceActivity {
}
private static class ExtraSetSummaryOnChangeListener extends SimpleSetSummaryOnChangeListener {
private Preference.OnPreferenceChangeListener delegate;
private final Preference.OnPreferenceChangeListener prefChangeListener;
public ExtraSetSummaryOnChangeListener(Preference.OnPreferenceChangeListener delegate) {
this.delegate = delegate;
public ExtraSetSummaryOnChangeListener(Preference.OnPreferenceChangeListener prefChangeListener) {
this.prefChangeListener = prefChangeListener;
}
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
boolean result = delegate.onPreferenceChange(preference, value);
boolean result = prefChangeListener.onPreferenceChange(preference, value);
if (result) {
return super.onPreferenceChange(preference, value);
}
@ -72,14 +126,24 @@ public class AbstractSettingsActivity extends PreferenceActivity {
}
}
private static SimpleSetSummaryOnChangeListener sBindPreferenceSummaryToValueListener = new SimpleSetSummaryOnChangeListener();
private static final SimpleSetSummaryOnChangeListener sBindPreferenceSummaryToValueListener = new SimpleSetSummaryOnChangeListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
AbstractGBActivity.init(this);
IntentFilter filterLocal = new IntentFilter();
filterLocal.addAction(GBApplication.ACTION_QUIT);
filterLocal.addAction(GBApplication.ACTION_LANGUAGE_CHANGE);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true);
for (String prefKey : getPreferenceKeysWithSummary()) {
final Preference pref = findPreference(prefKey);
if (pref != null) {
@ -90,6 +154,21 @@ public class AbstractSettingsActivity extends PreferenceActivity {
}
}
@Override
protected void onResume() {
super.onResume();
if (isLanguageInvalid) {
isLanguageInvalid = false;
recreate();
}
}
@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
@ -141,4 +220,11 @@ public class AbstractSettingsActivity extends PreferenceActivity {
}
return super.onOptionsItemSelected(item);
}
public void setLanguage(Locale language, boolean invalidateLanguage) {
if (invalidateLanguage) {
isLanguageInvalid = true;
}
AndroidUtils.setLanguage(this, language);
}
}

View File

@ -1,53 +1,117 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.format.DateFormat;
import android.view.MenuItem;
import android.widget.CheckBox;
import android.view.View;
import android.widget.CheckedTextView;
import android.widget.TimePicker;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
public class AlarmDetails extends Activity {
public class AlarmDetails extends AbstractGBActivity {
private GBAlarm alarm;
private TimePicker timePicker;
private CheckBox cbSmartWakeup;
private CheckBox cbMonday;
private CheckBox cbTuesday;
private CheckBox cbWednesday;
private CheckBox cbThursday;
private CheckBox cbFriday;
private CheckBox cbSaturday;
private CheckBox cbSunday;
private CheckedTextView cbSmartWakeup;
private CheckedTextView cbMonday;
private CheckedTextView cbTuesday;
private CheckedTextView cbWednesday;
private CheckedTextView cbThursday;
private CheckedTextView cbFriday;
private CheckedTextView cbSaturday;
private CheckedTextView cbSunday;
private GBDevice device;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_alarm_details);
Parcelable p = getIntent().getExtras().getParcelable("alarm");
alarm = (GBAlarm) p;
alarm = getIntent().getParcelableExtra("alarm");
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
timePicker = (TimePicker) findViewById(R.id.alarm_time_picker);
cbSmartWakeup = (CheckBox) findViewById(R.id.alarm_cb_smart_wakeup);
cbMonday = (CheckBox) findViewById(R.id.alarm_cb_mon);
cbTuesday = (CheckBox) findViewById(R.id.alarm_cb_tue);
cbWednesday = (CheckBox) findViewById(R.id.alarm_cb_wed);
cbThursday = (CheckBox) findViewById(R.id.alarm_cb_thu);
cbFriday = (CheckBox) findViewById(R.id.alarm_cb_fri);
cbSaturday = (CheckBox) findViewById(R.id.alarm_cb_sat);
cbSunday = (CheckBox) findViewById(R.id.alarm_cb_sun);
cbSmartWakeup = (CheckedTextView) findViewById(R.id.alarm_cb_smart_wakeup);
cbMonday = (CheckedTextView) findViewById(R.id.alarm_cb_monday);
cbTuesday = (CheckedTextView) findViewById(R.id.alarm_cb_tuesday);
cbWednesday = (CheckedTextView) findViewById(R.id.alarm_cb_wednesday);
cbThursday = (CheckedTextView) findViewById(R.id.alarm_cb_thursday);
cbFriday = (CheckedTextView) findViewById(R.id.alarm_cb_friday);
cbSaturday = (CheckedTextView) findViewById(R.id.alarm_cb_saturday);
cbSunday = (CheckedTextView) findViewById(R.id.alarm_cb_sunday);
cbSmartWakeup.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((CheckedTextView) v).toggle();
}
});
cbMonday.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((CheckedTextView) v).toggle();
}
});
cbTuesday.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((CheckedTextView) v).toggle();
}
});
cbWednesday.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((CheckedTextView) v).toggle();
}
});
cbThursday.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((CheckedTextView) v).toggle();
}
});
cbFriday.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((CheckedTextView) v).toggle();
}
});
cbSaturday.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((CheckedTextView) v).toggle();
}
});
cbSunday.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((CheckedTextView) v).toggle();
}
});
timePicker.setIs24HourView(DateFormat.is24HourFormat(GBApplication.getContext()));
timePicker.setCurrentHour(alarm.getHour());
timePicker.setCurrentMinute(alarm.getMinute());
cbSmartWakeup.setChecked(alarm.isSmartWakeup());
int smartAlarmVisibility = supportsSmartWakeup() ? View.VISIBLE : View.GONE;
cbSmartWakeup.setVisibility(smartAlarmVisibility);
cbMonday.setChecked(alarm.getRepetition(GBAlarm.ALARM_MON));
cbTuesday.setChecked(alarm.getRepetition(GBAlarm.ALARM_TUE));
@ -59,12 +123,19 @@ public class AlarmDetails extends Activity {
}
private boolean supportsSmartWakeup() {
if (device != null) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
return coordinator.supportsSmartWakeup(device);
}
return false;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// back button
updateAlarm();
finish();
return true;
}
@ -72,10 +143,16 @@ public class AlarmDetails extends Activity {
}
private void updateAlarm() {
alarm.setSmartWakeup(cbSmartWakeup.isChecked());
alarm.setSmartWakeup(supportsSmartWakeup() && cbSmartWakeup.isChecked());
alarm.setRepetition(cbMonday.isChecked(), cbTuesday.isChecked(), cbWednesday.isChecked(), cbThursday.isChecked(), cbFriday.isChecked(), cbSaturday.isChecked(), cbSunday.isChecked());
alarm.setHour(timePicker.getCurrentHour());
alarm.setMinute(timePicker.getCurrentMinute());
alarm.store();
}
@Override
protected void onPause() {
updateAlarm();
super.onPause();
}
}

View File

@ -1,11 +1,26 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.os.Bundle;
import nodomain.freeyourgadget.gadgetbridge.R;
public class AndroidPairingActivity extends Activity {
public class AndroidPairingActivity extends AbstractGBActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {

View File

@ -1,107 +1,67 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.support.v4.content.LocalBroadcastManager;
import android.view.LayoutInflater;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
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.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.AppBlacklistAdapter;
public class AppBlacklistActivity extends Activity {
public class AppBlacklistActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(AppBlacklistActivity.class);
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ControlCenter.ACTION_QUIT)) {
finish();
}
}
};
private SharedPreferences sharedPrefs;
private AppBlacklistAdapter appBlacklistAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_appblacklist);
getActionBar().setDisplayHomeAsUpEnabled(true);
RecyclerView appListView = (RecyclerView) findViewById(R.id.appListView);
appListView.setLayoutManager(new LinearLayoutManager(this));
final PackageManager pm = getPackageManager();
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
appBlacklistAdapter = new AppBlacklistAdapter(R.layout.item_app_blacklist, this);
final List<ApplicationInfo> packageList = pm.getInstalledApplications(PackageManager.GET_META_DATA);
ListView appListView = (ListView) findViewById(R.id.appListView);
appListView.setAdapter(appBlacklistAdapter);
final ArrayAdapter<ApplicationInfo> adapter = new ArrayAdapter<ApplicationInfo>(this, R.layout.item_with_checkbox, packageList) {
SearchView searchView = (SearchView) findViewById(R.id.appListViewSearch);
searchView.setIconifiedByDefault(false);
searchView.setIconified(false);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.item_with_checkbox, parent, false);
}
ApplicationInfo appInfo = packageList.get(position);
TextView deviceAppVersionAuthorLabel = (TextView) view.findViewById(R.id.item_details);
TextView deviceAppNameLabel = (TextView) view.findViewById(R.id.item_name);
ImageView deviceImageView = (ImageView) view.findViewById(R.id.item_image);
CheckBox checkbox = (CheckBox) view.findViewById(R.id.item_checkbox);
deviceAppVersionAuthorLabel.setText(appInfo.packageName);
deviceAppNameLabel.setText(appInfo.loadLabel(pm));
deviceImageView.setImageDrawable(appInfo.loadIcon(pm));
checkbox.setChecked(GBApplication.blacklist.contains(appInfo.packageName));
return view;
public boolean onQueryTextSubmit(String query) {
return false;
}
};
appListView.setAdapter(adapter);
appListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View v, int position, long id) {
String packageName = packageList.get(position).packageName;
CheckBox checkBox = ((CheckBox) v.findViewById(R.id.item_checkbox));
checkBox.toggle();
if (checkBox.isChecked()) {
GBApplication.addToBlacklist(packageName);
} else {
GBApplication.removeFromBlacklist(packageName);
}
public boolean onQueryTextChange(String newText) {
appBlacklistAdapter.getFilter().filter(newText);
return true;
}
});
IntentFilter filter = new IntentFilter();
filter.addAction(ControlCenter.ACTION_QUIT);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
}
@Override
@ -113,10 +73,4 @@ public class AppBlacklistActivity extends Activity {
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
super.onDestroy();
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
*
* This technique can be used with an {@link android.app.Activity} class, not just
* {@link android.preference.PreferenceActivity}.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
private AppCompatDelegate mDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
@Override
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
}
return mDelegate;
}
}

View File

@ -1,176 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.support.v4.content.LocalBroadcastManager;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
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.adapter.GBDeviceAppAdapter;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
public class AppManagerActivity extends Activity {
public static final String ACTION_REFRESH_APPLIST
= "nodomain.freeyourgadget.gadgetbridge.appmanager.action.refresh_applist";
private static final Logger LOG = LoggerFactory.getLogger(AppManagerActivity.class);
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ControlCenter.ACTION_QUIT)) {
finish();
} else if (action.equals(ACTION_REFRESH_APPLIST)) {
appList.clear();
int appCount = intent.getIntExtra("app_count", 0);
for (Integer i = 0; i < appCount; i++) {
String appName = intent.getStringExtra("app_name" + i.toString());
String appCreator = intent.getStringExtra("app_creator" + i.toString());
UUID uuid = UUID.fromString(intent.getStringExtra("app_uuid" + i.toString()));
GBDeviceApp.Type appType = GBDeviceApp.Type.values()[intent.getIntExtra("app_type" + i.toString(), 0)];
appList.add(new GBDeviceApp(uuid, appName, appCreator, "", appType));
}
if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
appList.addAll(getSystemApps());
}
mGBDeviceAppAdapter.notifyDataSetChanged();
}
}
};
private SharedPreferences sharedPrefs;
private final List<GBDeviceApp> appList = new ArrayList<>();
private GBDeviceAppAdapter mGBDeviceAppAdapter;
private GBDeviceApp selectedApp = null;
private List<GBDeviceApp> getSystemApps() {
List<GBDeviceApp> systemApps = new ArrayList<>();
systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.UNKNOWN));
systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.UNKNOWN));
return systemApps;
}
private List<GBDeviceApp> getCachedApps() {
List<GBDeviceApp> cachedAppList = new ArrayList<>();
try {
File cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache");
File files[] = cachePath.listFiles();
if (files != null) {
for (File file : files) {
if (file.getName().endsWith(".pbw")) {
UUID uuid = UUID.fromString(file.getName().substring(0, file.getName().length() - 4));
cachedAppList.add(new GBDeviceApp(uuid, uuid.toString(), "N/A", "", GBDeviceApp.Type.UNKNOWN));
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return cachedAppList;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
setContentView(R.layout.activity_appmanager);
getActionBar().setDisplayHomeAsUpEnabled(true);
ListView appListView = (ListView) findViewById(R.id.appListView);
mGBDeviceAppAdapter = new GBDeviceAppAdapter(this, appList);
appListView.setAdapter(this.mGBDeviceAppAdapter);
appListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View v, int position, long id) {
UUID uuid = appList.get(position).getUUID();
GBApplication.deviceService().onAppStart(uuid, true);
}
});
registerForContextMenu(appListView);
appList.addAll(getCachedApps());
if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
appList.addAll(getSystemApps());
}
IntentFilter filter = new IntentFilter();
filter.addAction(ControlCenter.ACTION_QUIT);
filter.addAction(ACTION_REFRESH_APPLIST);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
GBApplication.deviceService().onAppInfoReq();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
getMenuInflater().inflate(
R.menu.appmanager_context, menu);
AdapterView.AdapterContextMenuInfo acmi = (AdapterView.AdapterContextMenuInfo) menuInfo;
selectedApp = appList.get(acmi.position);
menu.setHeaderTitle(selectedApp.getName());
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.appmanager_app_delete:
if (selectedApp != null) {
GBApplication.deviceService().onAppDelete(selectedApp.getUUID());
}
return true;
default:
return super.onContextItemSelected(item);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
super.onDestroy();
}
}

View File

@ -0,0 +1,168 @@
/* 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.activities;
import android.os.Bundle;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Switch;
import android.widget.SeekBar;
import android.widget.TextView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.here.HereConstants;
import nodomain.freeyourgadget.gadgetbridge.entities.AudioEffect;
import nodomain.freeyourgadget.gadgetbridge.entities.AudioEffectType;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
public class AudioSettingsActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(AudioSettingsActivity.class);
private SeekBar seekBar;
private TextView volume_text;
// private boolean[] enabledEffects;
private int volume;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_settings);
LOG.debug("Create Audio Settings interface");
// FIXME: read enabled effects too
// FIXME: and EQ values XD
seekBar = (SeekBar) findViewById(R.id.volume_seekbar);
volume_text = (TextView) findViewById(R.id.volume_seekbar_volume);
int startingVolume = 30; // FIXME: how do I read it?
seekBar.setProgress(startingVolume);
setdB(startingVolume);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int position, boolean fromUser) {
// HERE's volume range is from 0xdb (-30dB on their app) to 0xff (+6dB)
// 0xff - 0xdb = 36 -> seekbar max value
// volume = seekbar + Min_volume (= 0xdb = 219)
volume = position + 219;
// LOG.debug("Volume = " + (byte)volume + " = " + volume + "= " + position);
AudioEffect eff = new AudioEffect(AudioEffectType.VOLUME, volume);
GBApplication.deviceService().onSetAudioProperty(eff);
// Show the volume on the UI in dB (range -30;6)
setdB(position);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
// FIXME: we need to read the current value and display this.
// right now, I'm just showing 0 dB, Called after changeListener sets the value on
// the device too.
Map<Integer, AudioEffectType> switchIds = new HashMap<Integer, AudioEffectType>();
switchIds.put(R.id.audio_effect_echo, AudioEffectType.ECHO);
switchIds.put(R.id.audio_effect_bassboost, AudioEffectType.BASSBOOST);
switchIds.put(R.id.audio_effect_fuzz, AudioEffectType.FUZZ);
switchIds.put(R.id.audio_effect_flange, AudioEffectType.FLANGE);
switchIds.put(R.id.audio_effect_reverb, AudioEffectType.REVERB);
switchIds.put(R.id.audio_effect_noisemask, AudioEffectType.NOISEMASK);
switchIds.put(R.id.audio_effect_bitcrusher, AudioEffectType.BITCRUSHER);
switchIds.put(R.id.audio_effect_chorus, AudioEffectType.CHORUS);
for (int id : switchIds.keySet()) {
final AudioEffectType effect = switchIds.get(id);
Switch s = (Switch) findViewById(id);
s.setOnCheckedChangeListener(
new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
LOG.info("Toggled " + effect.name());
applyEffect(new AudioEffect(effect, isChecked));
}
});
}
List<String> defaultPresets = new ArrayList<String>();
defaultPresets.add("8bit");
defaultPresets.add("Example");
RadioGroup presets = (RadioGroup)findViewById(R.id.audio_presets);
for (String preset : defaultPresets) {
RadioButton radioButton = new RadioButton(getBaseContext());
radioButton.setText(preset);
presets.addView(radioButton);
radioButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
RadioButton r = (RadioButton) findViewById(getCheckedPreset());
applyPreset(r.getText().toString());
}
});
}
}
private int getCheckedPreset() {
return ((RadioGroup)findViewById(R.id.audio_presets)).getCheckedRadioButtonId();
}
private void setdB(int volume) {
volume_text.setText(" " + (volume - 30) + " dB");
}
private void applyPreset(String preset) {
LOG.info("Applying preset " + preset);
switch(preset) {
case "8bit":
LOG.debug("8bit");
ArrayList<Object> values = new ArrayList<Object>();
values.add("3byte");
values.add(256.0f); // bits
values.add(20000.0f); // freq
AudioEffect effect = new AudioEffect(AudioEffectType.BITCRUSHER,
true, values);
GBApplication.deviceService().onSetAudioProperty(effect);
break;
default:
LOG.error("Missing preset! (Programming error!?");
}
}
private void applyEffect(AudioEffect effect) {
GBApplication.deviceService().onSetAudioProperty(effect);
}
}

View File

@ -0,0 +1,152 @@
/* Copyright (C) 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.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 AbstractGBActivity {
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;
}
}
}

View File

@ -1,10 +1,26 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.ListActivity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.MenuItem;
import java.util.Arrays;
@ -14,48 +30,71 @@ import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.GBAlarmListAdapter;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ALARMS;
public class ConfigureAlarms extends ListActivity {
public class ConfigureAlarms extends AbstractGBActivity {
private static final int REQ_CONFIGURE_ALARM = 1;
private GBAlarmListAdapter mGBAlarmListAdapter;
private Set<String> preferencesAlarmListSet;
private boolean avoidSendAlarmsToDevice;
private GBDevice device;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_configure_alarms);
getActionBar().setDisplayHomeAsUpEnabled(true);
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
preferencesAlarmListSet = sharedPrefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>());
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
Prefs prefs = GBApplication.getPrefs();
preferencesAlarmListSet = prefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>());
if (preferencesAlarmListSet.isEmpty()) {
//initialize the preferences
preferencesAlarmListSet = new HashSet<>(Arrays.asList(GBAlarm.DEFAULT_ALARMS));
sharedPrefs.edit().putStringSet(PREF_MIBAND_ALARMS, preferencesAlarmListSet).commit();
prefs.getPreferences().edit().putStringSet(PREF_MIBAND_ALARMS, preferencesAlarmListSet).apply();
}
mGBAlarmListAdapter = new GBAlarmListAdapter(this, preferencesAlarmListSet);
setListAdapter(mGBAlarmListAdapter);
RecyclerView alarmsRecyclerView = (RecyclerView) findViewById(R.id.alarm_list);
alarmsRecyclerView.setHasFixedSize(true);
alarmsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
alarmsRecyclerView.setAdapter(mGBAlarmListAdapter);
updateAlarmsFromPrefs();
}
@Override
protected void onResume() {
super.onResume();
protected void onPause() {
if (!avoidSendAlarmsToDevice) {
sendAlarmsToDevice();
}
super.onPause();
}
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
preferencesAlarmListSet = sharedPrefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>());
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQ_CONFIGURE_ALARM) {
avoidSendAlarmsToDevice = false;
updateAlarmsFromPrefs();
}
}
mGBAlarmListAdapter.setAlarmList(preferencesAlarmListSet);
private void updateAlarmsFromPrefs() {
Prefs prefs = GBApplication.getPrefs();
preferencesAlarmListSet = prefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>());
int reservedSlots = prefs.getInt(MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, 0);
mGBAlarmListAdapter.setAlarmList(preferencesAlarmListSet, reservedSlots);
mGBAlarmListAdapter.notifyDataSetChanged();
sendAlarmsToDevice();
}
@Override
@ -63,7 +102,6 @@ public class ConfigureAlarms extends ListActivity {
switch (item.getItemId()) {
case android.R.id.home:
// back button
sendAlarmsToDevice();
finish();
return true;
}
@ -71,10 +109,15 @@ public class ConfigureAlarms extends ListActivity {
}
public void configureAlarm(GBAlarm alarm) {
Intent startIntent;
startIntent = new Intent(getApplicationContext(), AlarmDetails.class);
avoidSendAlarmsToDevice = true;
Intent startIntent = new Intent(getApplicationContext(), AlarmDetails.class);
startIntent.putExtra("alarm", alarm);
startActivity(startIntent);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, getDevice());
startActivityForResult(startIntent, REQ_CONFIGURE_ALARM);
}
private GBDevice getDevice() {
return device;
}
private void sendAlarmsToDevice() {

View File

@ -1,348 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAdapter;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class ControlCenter extends Activity {
private static final Logger LOG = LoggerFactory.getLogger(ControlCenter.class);
public static final String ACTION_QUIT
= "nodomain.freeyourgadget.gadgetbridge.controlcenter.action.quit";
public static final String ACTION_REFRESH_DEVICELIST
= "nodomain.freeyourgadget.gadgetbridge.controlcenter.action.set_version";
private TextView hintTextView;
private ListView deviceListView;
private GBDeviceAdapter mGBDeviceAdapter;
private GBDevice selectedDevice = null;
private final List<GBDevice> deviceList = new ArrayList<>();
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case ACTION_QUIT:
finish();
break;
case ACTION_REFRESH_DEVICELIST:
case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
refreshPairedDevices();
break;
case GBDevice.ACTION_DEVICE_CHANGED:
GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
if (dev.getAddress() != null) {
int index = deviceList.indexOf(dev); // search by address
if (index >= 0) {
deviceList.set(index, dev);
} else {
deviceList.add(dev);
}
}
refreshPairedDevices();
refreshBusyState(dev);
break;
}
}
};
private void refreshBusyState(GBDevice dev) {
mGBDeviceAdapter.notifyDataSetChanged();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_controlcenter);
hintTextView = (TextView) findViewById(R.id.hintTextView);
deviceListView = (ListView) findViewById(R.id.deviceListView);
mGBDeviceAdapter = new GBDeviceAdapter(this, deviceList);
deviceListView.setAdapter(this.mGBDeviceAdapter);
deviceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View v, int position, long id) {
GBDevice gbDevice = deviceList.get(position);
if (gbDevice.isConnected()) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
Class<? extends Activity> primaryActivity = coordinator.getPrimaryActivity();
if (primaryActivity != null) {
Intent startIntent = new Intent(ControlCenter.this, primaryActivity);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, gbDevice);
startActivity(startIntent);
}
} else {
GBApplication.deviceService().connect(deviceList.get(position).getAddress());
}
}
});
registerForContextMenu(deviceListView);
IntentFilter filterLocal = new IntentFilter();
filterLocal.addAction(ACTION_QUIT);
filterLocal.addAction(ACTION_REFRESH_DEVICELIST);
filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
filterLocal.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
refreshPairedDevices();
/*
* Ask for permission to intercept notifications on first run.
*/
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPrefs.getBoolean("firstrun", true)) {
sharedPrefs.edit().putBoolean("firstrun", false).apply();
Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
startActivity(enableIntent);
}
GBApplication.deviceService().start();
if (GB.isBluetoothEnabled() && deviceList.isEmpty()) {
// start discovery when no devices are present
startActivity(new Intent(this, DiscoveryActivity.class));
} else {
GBApplication.deviceService().requestDeviceInfo();
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
AdapterView.AdapterContextMenuInfo acmi = (AdapterView.AdapterContextMenuInfo) menuInfo;
selectedDevice = deviceList.get(acmi.position);
if (selectedDevice != null && selectedDevice.isBusy()) {
// no context menu when device is busy
return;
}
getMenuInflater().inflate(R.menu.controlcenter_context, menu);
if (!selectedDevice.isConnected() || selectedDevice.getType() == DeviceType.PEBBLE) {
menu.removeItem(R.id.controlcenter_fetch_activity_data);
menu.removeItem(R.id.controlcenter_configure_alarms);
}
if (!selectedDevice.isConnected() || selectedDevice.getType() == DeviceType.MIBAND) {
menu.removeItem(R.id.controlcenter_take_screenshot);
}
if (selectedDevice.getState() == GBDevice.State.NOT_CONNECTED) {
menu.removeItem(R.id.controlcenter_disconnect);
}
if (!selectedDevice.isInitialized()) {
menu.removeItem(R.id.controlcenter_find_device);
}
menu.setHeaderTitle(selectedDevice.getName());
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.controlcenter_start_sleepmonitor:
if (selectedDevice != null) {
Intent startIntent;
startIntent = new Intent(ControlCenter.this, ChartsActivity.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, selectedDevice);
startActivity(startIntent);
}
return true;
case R.id.controlcenter_fetch_activity_data:
if (selectedDevice != null) {
GBApplication.deviceService().onFetchActivityData();
}
return true;
case R.id.controlcenter_disconnect:
if (selectedDevice != null) {
selectedDevice = null;
GBApplication.deviceService().disconnect();
}
return true;
case R.id.controlcenter_find_device:
if (selectedDevice != null) {
findDevice(true);
ProgressDialog.show(
this,
getString(R.string.control_center_find_lost_device),
getString(R.string.control_center_cancel_to_stop_vibration),
true, true,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
findDevice(false);
}
});
}
return true;
case R.id.controlcenter_configure_alarms:
if (selectedDevice != null) {
Intent startIntent;
startIntent = new Intent(ControlCenter.this, ConfigureAlarms.class);
startActivity(startIntent);
}
return true;
case R.id.controlcenter_take_screenshot:
if (selectedDevice != null) {
GBApplication.deviceService().onScreenshotReq();
}
return true;
default:
return super.onContextItemSelected(item);
}
}
private void findDevice(boolean start) {
GBApplication.deviceService().onFindDevice(start);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
Intent settingsIntent = new Intent(this, SettingsActivity.class);
startActivity(settingsIntent);
return true;
case R.id.action_debug:
Intent debugIntent = new Intent(this, DebugActivity.class);
startActivity(debugIntent);
return true;
case R.id.action_quit:
GBApplication.deviceService().quit();
Intent quitIntent = new Intent(ControlCenter.ACTION_QUIT);
LocalBroadcastManager.getInstance(this).sendBroadcast(quitIntent);
return true;
case R.id.action_discover:
Intent discoverIntent = new Intent(this, DiscoveryActivity.class);
startActivity(discoverIntent);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
unregisterReceiver(mReceiver);
super.onDestroy();
}
private void refreshPairedDevices() {
boolean connected = false;
List<GBDevice> availableDevices = new ArrayList<>();
for (GBDevice device : deviceList) {
if (device.isConnected() || device.isConnecting()) {
connected = true;
availableDevices.add(device);
}
}
BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
if (btAdapter == null) {
Toast.makeText(this, R.string.bluetooth_is_not_supported_, Toast.LENGTH_SHORT).show();
} else if (!btAdapter.isEnabled()) {
Toast.makeText(this, R.string.bluetooth_is_disabled_, Toast.LENGTH_SHORT).show();
} else {
Set<BluetoothDevice> pairedDevices = btAdapter.getBondedDevices();
for (BluetoothDevice pairedDevice : pairedDevices) {
DeviceType deviceDeviceType;
if (pairedDevice.getName().indexOf("Pebble") == 0) {
deviceDeviceType = DeviceType.PEBBLE;
} else if (pairedDevice.getName().equals("MI")) {
deviceDeviceType = DeviceType.MIBAND;
} else {
continue;
}
GBDevice device = new GBDevice(pairedDevice.getAddress(), pairedDevice.getName(), deviceDeviceType);
if (!availableDevices.contains(device)) {
availableDevices.add(device);
}
}
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
String miAddr = sharedPrefs.getString(MiBandConst.PREF_MIBAND_ADDRESS, "");
if (miAddr.length() > 0) {
GBDevice miDevice = new GBDevice(miAddr, "MI", DeviceType.MIBAND);
if (!availableDevices.contains(miDevice)) {
availableDevices.add(miDevice);
}
}
String pebbleEmuAddr = sharedPrefs.getString("pebble_emu_addr", "");
String pebbleEmuPort = sharedPrefs.getString("pebble_emu_port", "");
if (pebbleEmuAddr.length() >= 7 && pebbleEmuPort.length() > 0) {
GBDevice pebbleEmuDevice = new GBDevice(pebbleEmuAddr + ":" + pebbleEmuPort, "Pebble qemu", DeviceType.PEBBLE);
if (!availableDevices.contains(pebbleEmuDevice)) {
availableDevices.add(pebbleEmuDevice);
}
}
deviceList.retainAll(availableDevices);
for (GBDevice dev : availableDevices) {
if (!deviceList.contains(dev)) {
deviceList.add(dev);
}
}
if (connected) {
hintTextView.setText(R.string.tap_connected_device_for_app_mananger);
} else if (!deviceList.isEmpty()) {
hintTextView.setText(R.string.tap_a_device_to_connect);
}
}
mGBDeviceAdapter.notifyDataSetChanged();
}
}

View File

@ -0,0 +1,331 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.Manifest;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
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;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.NavigationView;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
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;
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;
//TODO: extend AbstractGBActivity, but it requires actionbar that is not available
public class ControlCenterv2 extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, GBActivity {
//needed for KK compatibility
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
private DeviceManager deviceManager;
private ImageView background;
private List<GBDevice> deviceList;
private GBDeviceAdapterv2 mGBDeviceAdapter;
private RecyclerView deviceListView;
private boolean isLanguageInvalid = false;
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(), true);
break;
case GBApplication.ACTION_QUIT:
finish();
break;
case DeviceManager.ACTION_DEVICES_CHANGED:
refreshPairedDevices();
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
AbstractGBActivity.init(this, AbstractGBActivity.NO_ACTIONBAR);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_controlcenterv2);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
launchDiscoveryActivity();
}
});
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.controlcenter_navigation_drawer_open, R.string.controlcenter_navigation_drawer_close);
drawer.setDrawerListener(toggle);
toggle.syncState();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
//end of material design boilerplate
deviceManager = ((GBApplication) getApplication()).getDeviceManager();
deviceListView = (RecyclerView) findViewById(R.id.deviceListView);
deviceListView.setHasFixedSize(true);
deviceListView.setLayoutManager(new LinearLayoutManager(this));
background = (ImageView) findViewById(R.id.no_items_bg);
deviceList = deviceManager.getDevices();
mGBDeviceAdapter = new GBDeviceAdapterv2(this, deviceList);
deviceListView.setAdapter(this.mGBDeviceAdapter);
ItemTouchHelper swipeToDismissTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
ItemTouchHelper.LEFT , ItemTouchHelper.RIGHT) {
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
if(dX>50)
dX = 50;
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
GB.toast(getBaseContext(), "onMove", Toast.LENGTH_LONG, GB.ERROR);
return false;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
GB.toast(getBaseContext(), "onSwiped", Toast.LENGTH_LONG, GB.ERROR);
}
@Override
public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder, float dX, float dY,
int actionState, boolean isCurrentlyActive) {
}
});
//uncomment to enable fixed-swipe to reveal more actions
//swipeToDismissTouchHelper.attachToRecyclerView(deviceListView);
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);
refreshPairedDevices();
/*
* Ask for permission to intercept notifications on first run.
*/
Prefs prefs = GBApplication.getPrefs();
if (prefs.getBoolean("firstrun", true)) {
prefs.getPreferences().edit().putBoolean("firstrun", false).apply();
Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
startActivity(enableIntent);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
checkAndRequestPermissions();
}
ChangeLog cl = createChangeLog();
if (cl.isFirstRun()) {
cl.getLogDialog().show();
}
GBApplication.deviceService().start();
if (GB.isBluetoothEnabled() && deviceList.isEmpty() && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
startActivity(new Intent(this, DiscoveryActivity.class));
} else {
GBApplication.deviceService().requestDeviceInfo();
}
}
@Override
protected void onResume() {
super.onResume();
if (isLanguageInvalid) {
isLanguageInvalid = false;
recreate();
}
}
@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);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
switch (item.getItemId()) {
case R.id.action_settings:
Intent settingsIntent = new Intent(this, SettingsActivity.class);
startActivity(settingsIntent);
return true;
case R.id.action_debug:
Intent debugIntent = new Intent(this, DebugActivity.class);
startActivity(debugIntent);
return true;
case R.id.action_db_management:
Intent dbIntent = new Intent(this, DbManagementActivity.class);
startActivity(dbIntent);
return true;
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 = createChangeLog();
cl.getFullLogDialog().show();
return true;
}
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));
}
private void refreshPairedDevices() {
List<GBDevice> deviceList = deviceManager.getDevices();
if (deviceList.isEmpty()) {
background.setVisibility(View.VISIBLE);
} else {
background.setVisibility(View.INVISIBLE);
}
mGBDeviceAdapter.notifyDataSetChanged();
}
@TargetApi(Build.VERSION_CODES.M)
private void checkAndRequestPermissions() {
List<String> wantedPermissions = new ArrayList<>();
if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.BLUETOOTH);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.BLUETOOTH_ADMIN);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_CONTACTS);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.CALL_PHONE);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_PHONE_STATE);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.PROCESS_OUTGOING_CALLS) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_SMS);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.SEND_SMS);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_CALENDAR);
if (!wantedPermissions.isEmpty())
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[wantedPermissions.size()]), 0);
}
public void setLanguage(Locale language, boolean invalidateLanguage) {
if (invalidateLanguage) {
isLanguageInvalid = true;
}
AndroidUtils.setLanguage(this, language);
}
}

View File

@ -0,0 +1,237 @@
/* Copyright (C) 2016-2017 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.ImportExportSharedPreferences;
public class DbManagementActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(DbManagementActivity.class);
private static SharedPreferences sharedPrefs;
private ImportExportSharedPreferences shared_file = new ImportExportSharedPreferences();
private Button exportDBButton;
private Button importDBButton;
private Button deleteOldActivityDBButton;
private Button deleteDBButton;
private TextView dbPath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_db_management);
dbPath = (TextView) findViewById(R.id.activity_db_management_path);
dbPath.setText(getExternalPath());
exportDBButton = (Button) findViewById(R.id.exportDBButton);
exportDBButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
exportDB();
}
});
importDBButton = (Button) findViewById(R.id.importDBButton);
importDBButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
importDB();
}
});
int oldDBVisibility = hasOldActivityDatabase() ? View.VISIBLE : View.GONE;
deleteOldActivityDBButton = (Button) findViewById(R.id.deleteOldActivityDB);
deleteOldActivityDBButton.setVisibility(oldDBVisibility);
deleteOldActivityDBButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
deleteOldActivityDbFile();
}
});
deleteDBButton = (Button) findViewById(R.id.emptyDBButton);
deleteDBButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
deleteActivityDatabase();
}
});
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
}
private boolean hasOldActivityDatabase() {
return new DBHelper(this).existsDB("ActivityDatabase");
}
private String getExternalPath() {
try {
return FileUtils.getExternalFilesDir().getAbsolutePath();
} catch (Exception ex) {
LOG.warn("Unable to get external files dir", ex);
}
return getString(R.string.dbmanagementactivvity_cannot_access_export_path);
}
private void exportShared() {
// BEGIN EXAMPLE
File myPath = null;
try {
myPath = FileUtils.getExternalFilesDir();
File myFile = new File(myPath, "Export_preference");
shared_file.exportToFile(sharedPrefs,myFile,null);
} catch (IOException ex) {
GB.toast(this, getString(R.string.dbmanagementactivity_error_exporting_shared, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
}
}
private void importShared() {
// BEGIN EXAMPLE
File myPath = null;
try {
myPath = FileUtils.getExternalFilesDir();
File myFile = new File(myPath, "Export_preference");
shared_file.importFromFile(sharedPrefs,myFile );
} catch (Exception ex) {
GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_error_importing_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
}
}
private void exportDB() {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
exportShared();
DBHelper helper = new DBHelper(this);
File dir = FileUtils.getExternalFilesDir();
File destFile = helper.exportDB(dbHandler, dir);
GB.toast(this, getString(R.string.dbmanagementactivity_exported_to, destFile.getAbsolutePath()), Toast.LENGTH_LONG, GB.INFO);
} catch (Exception ex) {
GB.toast(this, getString(R.string.dbmanagementactivity_error_exporting_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
}
}
private void importDB() {
new AlertDialog.Builder(this)
.setCancelable(true)
.setTitle(R.string.dbmanagementactivity_import_data_title)
.setMessage(R.string.dbmanagementactivity_overwrite_database_confirmation)
.setPositiveButton(R.string.dbmanagementactivity_overwrite, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
importShared();
DBHelper helper = new DBHelper(DbManagementActivity.this);
File dir = FileUtils.getExternalFilesDir();
SQLiteOpenHelper sqLiteOpenHelper = dbHandler.getHelper();
File sourceFile = new File(dir, sqLiteOpenHelper.getDatabaseName());
helper.importDB(dbHandler, sourceFile);
helper.validateDB(sqLiteOpenHelper);
GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_import_successful), Toast.LENGTH_LONG, GB.INFO);
} catch (Exception ex) {
GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_error_importing_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
}
}
})
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.show();
}
private void deleteActivityDatabase() {
new AlertDialog.Builder(this)
.setCancelable(true)
.setTitle(R.string.dbmanagementactivity_delete_activity_data_title)
.setMessage(R.string.dbmanagementactivity_really_delete_entire_db)
.setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (GBApplication.deleteActivityDatabase(DbManagementActivity.this)) {
GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_database_successfully_deleted), Toast.LENGTH_SHORT, GB.INFO);
} else {
GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_db_deletion_failed), Toast.LENGTH_SHORT, GB.INFO);
}
}
})
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.show();
}
private void deleteOldActivityDbFile() {
new AlertDialog.Builder(this).setCancelable(true);
new AlertDialog.Builder(this).setTitle(R.string.dbmanagementactivity_delete_old_activity_db);
new AlertDialog.Builder(this).setMessage(R.string.dbmanagementactivity_delete_old_activitydb_confirmation);
new AlertDialog.Builder(this).setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (GBApplication.deleteOldActivityDatabase(DbManagementActivity.this)) {
GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_old_activity_db_successfully_deleted), Toast.LENGTH_SHORT, GB.INFO);
} else {
GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_old_activity_db_deletion_failed), Toast.LENGTH_SHORT, GB.INFO);
}
}
});
new AlertDialog.Builder(this).setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
new AlertDialog.Builder(this).show();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -1,43 +1,67 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, ivanovlev, Kasha, Lem Dulfo, Steffen Liebergeld
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.app.Activity;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.support.v4.content.LocalBroadcastManager;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class DebugActivity extends Activity {
public class DebugActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(DebugActivity.class);
private Button sendSMSButton;
private Button sendEmailButton;
private static final String EXTRA_REPLY = "reply";
private static final String ACTION_REPLY
= "nodomain.freeyourgadget.gadgetbridge.DebugActivity.action.reply";
private Spinner sendTypeSpinner;
private Button sendButton;
private Button incomingCallButton;
private Button outgoingCallButton;
private Button startCallButton;
@ -46,15 +70,26 @@ public class DebugActivity extends Activity {
private Button setMusicInfoButton;
private Button setTimeButton;
private Button rebootButton;
private Button exportDBButton;
private Button importDBButton;
private EditText editContent;
private Button HeartRateButton;
private Button testNewFunctionalityButton;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
private EditText editContent;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ControlCenter.ACTION_QUIT)) {
finish();
switch (intent.getAction()) {
case ACTION_REPLY: {
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
CharSequence reply = remoteInput.getCharSequence(EXTRA_REPLY);
LOG.info("got wearable reply: " + reply);
GB.toast(context, "got wearable reply: " + reply, Toast.LENGTH_SHORT, GB.INFO);
break;
}
case DeviceService.ACTION_HEARTRATE_MEASUREMENT: {
int hrValue = intent.getIntExtra(DeviceService.EXTRA_HEART_RATE_VALUE, -1);
GB.toast(DebugActivity.this, "Heart Rate measured: " + hrValue, Toast.LENGTH_LONG, GB.INFO);
break;
}
}
}
};
@ -63,32 +98,35 @@ public class DebugActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_debug);
getActionBar().setDisplayHomeAsUpEnabled(true);
registerReceiver(mReceiver, new IntentFilter(ControlCenter.ACTION_QUIT));
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_REPLY);
filter.addAction(DeviceService.ACTION_HEARTRATE_MEASUREMENT);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
registerReceiver(mReceiver, filter); // for ACTION_REPLY
editContent = (EditText) findViewById(R.id.editContent);
sendSMSButton = (Button) findViewById(R.id.sendSMSButton);
sendSMSButton.setOnClickListener(new View.OnClickListener() {
ArrayList<String> spinnerArray = new ArrayList<>();
for (NotificationType notificationType : NotificationType.values()) {
spinnerArray.add(notificationType.name());
}
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, spinnerArray);
sendTypeSpinner = (Spinner) findViewById(R.id.sendTypeSpinner);
sendTypeSpinner.setAdapter(spinnerArrayAdapter);
sendButton = (Button) findViewById(R.id.sendButton);
sendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NotificationSpec notificationSpec = new NotificationSpec();
notificationSpec.sender = getResources().getText(R.string.app_name).toString();
notificationSpec.body = editContent.getText().toString();
notificationSpec.type = NotificationType.SMS;
notificationSpec.id = -1;
GBApplication.deviceService().onNotification(notificationSpec);
}
});
sendEmailButton = (Button) findViewById(R.id.sendEmailButton);
sendEmailButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NotificationSpec notificationSpec = new NotificationSpec();
notificationSpec.sender = getResources().getText(R.string.app_name).toString();
notificationSpec.subject = editContent.getText().toString();
notificationSpec.body = editContent.getText().toString();
notificationSpec.type = NotificationType.EMAIL;
String testString = editContent.getText().toString();
notificationSpec.phoneNumber = testString;
notificationSpec.body = testString;
notificationSpec.sender = testString;
notificationSpec.subject = testString;
notificationSpec.type = NotificationType.values()[sendTypeSpinner.getSelectedItemPosition()];
notificationSpec.pebbleColor = notificationSpec.type.color;
notificationSpec.id = -1;
GBApplication.deviceService().onNotification(notificationSpec);
}
@ -98,20 +136,20 @@ public class DebugActivity extends Activity {
incomingCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GBApplication.deviceService().onSetCallState(
editContent.getText().toString(),
null,
ServiceCommand.CALL_INCOMING);
CallSpec callSpec = new CallSpec();
callSpec.command = CallSpec.CALL_INCOMING;
callSpec.number = editContent.getText().toString();
GBApplication.deviceService().onSetCallState(callSpec);
}
});
outgoingCallButton = (Button) findViewById(R.id.outgoingCallButton);
outgoingCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GBApplication.deviceService().onSetCallState(
editContent.getText().toString(),
null,
ServiceCommand.CALL_OUTGOING);
CallSpec callSpec = new CallSpec();
callSpec.command = CallSpec.CALL_OUTGOING;
callSpec.number = editContent.getText().toString();
GBApplication.deviceService().onSetCallState(callSpec);
}
});
@ -119,35 +157,18 @@ public class DebugActivity extends Activity {
startCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GBApplication.deviceService().onSetCallState(
null,
null,
ServiceCommand.CALL_START);
CallSpec callSpec = new CallSpec();
callSpec.command = CallSpec.CALL_START;
GBApplication.deviceService().onSetCallState(callSpec);
}
});
endCallButton = (Button) findViewById(R.id.endCallButton);
endCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GBApplication.deviceService().onSetCallState(
null,
null,
ServiceCommand.CALL_END);
}
});
exportDBButton = (Button) findViewById(R.id.exportDBButton);
exportDBButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
exportDB();
}
});
importDBButton = (Button) findViewById(R.id.importDBButton);
importDBButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
importDB();
CallSpec callSpec = new CallSpec();
callSpec.command = CallSpec.CALL_END;
GBApplication.deviceService().onSetCallState(callSpec);
}
});
@ -158,15 +179,38 @@ public class DebugActivity extends Activity {
GBApplication.deviceService().onReboot();
}
});
HeartRateButton = (Button) findViewById(R.id.HearRateButton);
HeartRateButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GB.toast("Measuring heart rate, please wait...", Toast.LENGTH_LONG, GB.INFO);
GBApplication.deviceService().onHeartRateTest();
}
});
setMusicInfoButton = (Button) findViewById(R.id.setMusicInfoButton);
setMusicInfoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GBApplication.deviceService().onSetMusicInfo(
editContent.getText().toString() + "(artist)",
editContent.getText().toString() + "(album)",
editContent.getText().toString() + "(tracl)");
MusicSpec musicSpec = new MusicSpec();
String testString = editContent.getText().toString();
musicSpec.artist = testString + "(artist)";
musicSpec.album = testString + "(album)";
musicSpec.track = testString + "(track)";
musicSpec.duration = 10;
musicSpec.trackCount = 5;
musicSpec.trackNr = 2;
GBApplication.deviceService().onSetMusicInfo(musicSpec);
MusicStateSpec stateSpec = new MusicStateSpec();
stateSpec.position = 0;
stateSpec.state = 0x01; // playing
stateSpec.playRate = 100;
stateSpec.repeat = 1;
stateSpec.shuffle = 1;
GBApplication.deviceService().onSetMusicState(stateSpec);
}
});
@ -185,44 +229,18 @@ public class DebugActivity extends Activity {
testNotification();
}
});
testNewFunctionalityButton = (Button) findViewById(R.id.testNewFunctionality);
testNewFunctionalityButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
testNewFunctionality();
}
});
}
private void exportDB() {
DBHandler dbHandler = null;
try {
dbHandler = GBApplication.acquireDB();
DBHelper helper = new DBHelper(this);
File dir = FileUtils.getExternalFilesDir();
File destFile = helper.exportDB(dbHandler.getHelper(), dir);
GB.toast(this, "Exported to: " + destFile.getAbsolutePath(), Toast.LENGTH_LONG, GB.INFO);
} catch (Exception ex) {
LOG.error("Unable to export db", ex);
Toast.makeText(this, "Error exporting DB: " + ex.getMessage(), Toast.LENGTH_LONG).show();
} finally {
if (dbHandler != null) {
dbHandler.release();
}
}
}
private void importDB() {
DBHandler dbHandler = null;
try {
dbHandler = GBApplication.acquireDB();
DBHelper helper = new DBHelper(this);
File dir = FileUtils.getExternalFilesDir();
SQLiteOpenHelper sqLiteOpenHelper = dbHandler.getHelper();
File sourceFile = new File(dir, sqLiteOpenHelper.getDatabaseName());
helper.importDB(sqLiteOpenHelper, sourceFile);
helper.validateDB(sqLiteOpenHelper);
GB.toast(this, "Import successful.", Toast.LENGTH_LONG, GB.INFO);
} catch (Exception ex) {
GB.toast(this, "Error importing DB: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR);
} finally {
if (dbHandler != null) {
dbHandler.release();
}
}
private void testNewFunctionality() {
GBApplication.deviceService().onTestNewFunction();
}
private void testNotification() {
@ -233,13 +251,30 @@ public class DebugActivity extends Activity {
notificationIntent, 0);
NotificationManager nManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationCompat.Builder ncomp = new NotificationCompat.Builder(this);
ncomp.setContentTitle(getString(R.string.test_notification));
ncomp.setContentText(getString(R.string.this_is_a_test_notification_from_gadgetbridge));
ncomp.setTicker(getString(R.string.this_is_a_test_notification_from_gadgetbridge));
ncomp.setSmallIcon(R.drawable.ic_notification);
ncomp.setAutoCancel(true);
ncomp.setContentIntent(pendingIntent);
RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_REPLY)
.build();
Intent replyIntent = new Intent(ACTION_REPLY);
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(this, 0, replyIntent, 0);
NotificationCompat.Action action =
new NotificationCompat.Action.Builder(android.R.drawable.ic_input_add, "Reply", replyPendingIntent)
.addRemoteInput(remoteInput)
.build();
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender().addAction(action);
NotificationCompat.Builder ncomp = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.test_notification))
.setContentText(getString(R.string.this_is_a_test_notification_from_gadgetbridge))
.setTicker(getString(R.string.this_is_a_test_notification_from_gadgetbridge))
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.extend(wearableExtender);
nManager.notify((int) System.currentTimeMillis(), ncomp.build());
}
@ -256,7 +291,11 @@ public class DebugActivity extends Activity {
@Override
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
unregisterReceiver(mReceiver);
}
public interface DeviceSelectionCallback {
void invoke(GBDevice device);
}
}

View File

@ -1,17 +1,47 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, JohnnySun, Lem Dulfo, Uwe Hermann
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.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.Parcelable;
import android.support.v4.app.ActivityCompat;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
@ -23,35 +53,56 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.DeviceCandidateAdapter;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class DiscoveryActivity extends Activity implements AdapterView.OnItemClickListener {
import static android.bluetooth.le.ScanSettings.MATCH_MODE_STICKY;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_LATENCY;
public class DiscoveryActivity extends AbstractGBActivity implements AdapterView.OnItemClickListener {
private static final Logger LOG = LoggerFactory.getLogger(DiscoveryActivity.class);
private static final long SCAN_DURATION = 60000; // 60s
private Handler handler = new Handler();
private ScanCallback newLeScanCallback = null;
private BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
private final Handler handler = new Handler();
private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
discoveryStarted(Scanning.SCANNING_BT);
if (isScanning != Scanning.SCANNING_BTLE && isScanning != Scanning.SCANNING_NEW_BTLE) {
discoveryStarted(Scanning.SCANNING_BT);
}
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
// continue with LE scan, if available
if (isScanning == Scanning.SCANNING_BT) {
startDiscovery(Scanning.SCANNING_BTLE);
} else {
discoveryFinished();
}
handler.post(new Runnable() {
@Override
public void run() {
// continue with LE scan, if available
if (isScanning == Scanning.SCANNING_BT) {
checkAndRequestLocationPermission();
if (GBApplication.isRunningLollipopOrLater()) {
startDiscovery(Scanning.SCANNING_NEW_BTLE);
} else {
startDiscovery(Scanning.SCANNING_BTLE);
}
} else {
discoveryFinished();
}
}
});
break;
case BluetoothAdapter.ACTION_STATE_CHANGED:
int oldState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, BluetoothAdapter.STATE_OFF);
@ -64,13 +115,20 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
handleDeviceFound(device, rssi);
break;
}
case BluetoothDevice.ACTION_UUID: {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, GBDevice.RSSI_UNKNOWN);
Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
ParcelUuid[] uuids2 = AndroidUtils.toParcelUUids(uuids);
handleDeviceFound(device, rssi, uuids2);
break;
}
case BluetoothDevice.ACTION_BOND_STATE_CHANGED: {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device != null && device.getAddress().equals(bondingAddress)) {
if (device != null && bondingDevice != null && device.getAddress().equals(bondingDevice.getMacAddress())) {
int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
if (bondState == BluetoothDevice.BOND_BONDED) {
LOG.info("Successfully bonded with: " + bondingAddress);
finish();
handleDeviceBonded();
}
}
}
@ -78,14 +136,109 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
}
};
private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
private void connectAndFinish(GBDevice device) {
GB.toast(DiscoveryActivity.this, getString(R.string.discovery_trying_to_connect_to, device.getName()), Toast.LENGTH_SHORT, GB.INFO);
GBApplication.deviceService().connect(device, true);
finish();
}
private void createBond(final GBDeviceCandidate deviceCandidate, int bondingStyle) {
if (bondingStyle == DeviceCoordinator.BONDING_STYLE_NONE) {
return;
}
if (bondingStyle == DeviceCoordinator.BONDING_STYLE_ASK) {
new AlertDialog.Builder(this)
.setCancelable(true)
.setTitle(DiscoveryActivity.this.getString(R.string.discovery_pair_title, deviceCandidate.getName()))
.setMessage(DiscoveryActivity.this.getString(R.string.discovery_pair_question))
.setPositiveButton(DiscoveryActivity.this.getString(R.string.discovery_yes_pair), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
doCreatePair(deviceCandidate);
}
})
.setNegativeButton(R.string.discovery_dont_pair, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
connectAndFinish(device);
}
})
.show();
} else {
doCreatePair(deviceCandidate);
}
}
private void doCreatePair(GBDeviceCandidate deviceCandidate) {
GB.toast(DiscoveryActivity.this, getString(R.string.discovery_attempting_to_pair, deviceCandidate.getName()), Toast.LENGTH_SHORT, GB.INFO);
if (deviceCandidate.getDevice().createBond()) {
// async, wait for bonding event to finish this activity
LOG.info("Bonding in progress...");
bondingDevice = deviceCandidate;
} else {
GB.toast(DiscoveryActivity.this, getString(R.string.discovery_bonding_failed_immediately, deviceCandidate.getName()), Toast.LENGTH_SHORT, GB.ERROR);
}
}
private void handleDeviceBonded() {
GB.toast(DiscoveryActivity.this, getString(R.string.discovery_successfully_bonded, bondingDevice.getName()), Toast.LENGTH_SHORT, GB.INFO);
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(bondingDevice);
connectAndFinish(device);
}
private final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
LOG.warn(device.getName() + ": " + ((scanRecord != null) ? scanRecord.length : -1));
logMessageContent(scanRecord);
handleDeviceFound(device, (short) rssi);
}
};
private Runnable stopRunnable = new Runnable() {
// why use a method to get callback?
// because this callback need API >= 21
// we cant add @TARGETAPI("Lollipop") at class header
// so use a method with SDK check to return this callback
private ScanCallback getScanCallback() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
newLeScanCallback = new ScanCallback() {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
try {
ScanRecord scanRecord = result.getScanRecord();
ParcelUuid[] uuids = null;
if (scanRecord != null) {
//logMessageContent(scanRecord.getBytes());
List<ParcelUuid> serviceUuids = scanRecord.getServiceUuids();
if (serviceUuids != null) {
uuids = serviceUuids.toArray(new ParcelUuid[0]);
}
}
LOG.warn(result.getDevice().getName() + ": " +
((scanRecord != null) ? scanRecord.getBytes().length : -1));
handleDeviceFound(result.getDevice(), (short) result.getRssi(), uuids);
} catch (NullPointerException e) {
LOG.warn("Error handling scan result", e);
}
}
};
}
return newLeScanCallback;
}
public void logMessageContent(byte[] value) {
if (value != null) {
for (byte b : value) {
LOG.warn("DATA: " + String.format("0x%2x", b) + " - " + (char) (b & 0xff));
}
}
}
private final Runnable stopRunnable = new Runnable() {
@Override
public void run() {
stopDiscovery();
@ -94,15 +247,16 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
private ProgressBar progressView;
private BluetoothAdapter adapter;
private ArrayList<GBDeviceCandidate> deviceCandidates = new ArrayList<>();
private final ArrayList<GBDeviceCandidate> deviceCandidates = new ArrayList<>();
private DeviceCandidateAdapter cadidateListAdapter;
private Button startButton;
private Scanning isScanning = Scanning.SCANNING_OFF;
private String bondingAddress;
private GBDeviceCandidate bondingDevice;
private enum Scanning {
SCANNING_BT,
SCANNING_BTLE,
SCANNING_NEW_BTLE,
SCANNING_OFF
}
@ -131,6 +285,7 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
IntentFilter bluetoothIntents = new IntentFilter();
bluetoothIntents.addAction(BluetoothDevice.ACTION_FOUND);
bluetoothIntents.addAction(BluetoothDevice.ACTION_UUID);
bluetoothIntents.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
bluetoothIntents.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
bluetoothIntents.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
@ -170,13 +325,44 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
@Override
protected void onDestroy() {
unregisterReceiver(bluetoothReceiver);
try {
unregisterReceiver(bluetoothReceiver);
} catch (IllegalArgumentException e) {
LOG.warn("Tried to unregister Bluetooth Receiver that wasn't registered.");
}
super.onDestroy();
}
private void handleDeviceFound(BluetoothDevice device, short rssi) {
GBDeviceCandidate candidate = new GBDeviceCandidate(device, rssi);
if (DeviceHelper.getInstance().isSupported(candidate)) {
ParcelUuid[] uuids = device.getUuids();
if (uuids == null) {
if (device.fetchUuidsWithSdp()) {
return;
}
}
handleDeviceFound(device, rssi, uuids);
}
private void handleDeviceFound(BluetoothDevice device, short rssi, ParcelUuid[] uuids) {
LOG.debug("found device: " + device.getName() + ", " + device.getAddress());
if (LOG.isDebugEnabled()) {
if (uuids != null && uuids.length > 0) {
for (ParcelUuid uuid : uuids) {
LOG.debug(" supports uuid: " + uuid.toString());
}
}
}
if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
return; // ignore already bonded devices
}
GBDeviceCandidate candidate = new GBDeviceCandidate(device, rssi, uuids);
DeviceType deviceType = DeviceHelper.getInstance().getSupportedType(candidate);
if (deviceType.isSupported()) {
candidate.setDeviceType(deviceType);
LOG.info("Recognized supported device: " + candidate);
int index = deviceCandidates.indexOf(candidate);
if (index >= 0) {
deviceCandidates.set(index, candidate); // replace
@ -211,10 +397,16 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
} else {
discoveryFinished();
}
} else if (what == Scanning.SCANNING_NEW_BTLE) {
if (GB.supportsBluetoothLE()) {
startNEWBTLEDiscovery();
} else {
discoveryFinished();
}
}
} else {
discoveryFinished();
Toast.makeText(this, "Enable Bluetooth to discover devices.", Toast.LENGTH_LONG).show();
GB.toast(DiscoveryActivity.this, getString(R.string.discovery_enable_bluetooth), Toast.LENGTH_SHORT, GB.ERROR);
}
}
@ -234,6 +426,8 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
stopBTDiscovery();
} else if (wasScanning == Scanning.SCANNING_BTLE) {
stopBTLEDiscovery();
} else if (wasScanning == Scanning.SCANNING_NEW_BTLE) {
stopNewBTLEDiscovery();
}
handler.removeMessages(0, stopRunnable);
}
@ -247,9 +441,20 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
adapter.cancelDiscovery();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void stopNewBTLEDiscovery() {
adapter.getBluetoothLeScanner().stopScan(newLeScanCallback);
}
private void bluetoothStateChanged(int oldState, int newState) {
discoveryFinished();
startButton.setEnabled(newState == BluetoothAdapter.STATE_ON);
if (newState == BluetoothAdapter.STATE_ON) {
this.adapter = BluetoothAdapter.getDefaultAdapter();
startButton.setEnabled(true);
} else {
this.adapter = null;
startButton.setEnabled(false);
}
}
private void discoveryFinished() {
@ -284,8 +489,15 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
return false;
}
BluetoothAdapter adapter = bluetoothService.getAdapter();
if (adapter == null) {
LOG.warn("No bluetooth available");
this.adapter = null;
return false;
}
if (!adapter.isEnabled()) {
LOG.warn("Bluetooth not enabled");
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(enableBtIntent);
this.adapter = null;
return false;
}
@ -293,6 +505,41 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
return true;
}
// New BTLE Discovery use startScan (List<ScanFilter> filters,
// ScanSettings settings,
// ScanCallback callback)
// It's added on API21
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void startNEWBTLEDiscovery() {
// Only use new API when user uses Lollipop+ device
LOG.info("Start New BTLE Discovery");
handler.removeMessages(0, stopRunnable);
handler.sendMessageDelayed(getPostMessage(stopRunnable), SCAN_DURATION);
adapter.getBluetoothLeScanner().startScan(getScanFilters(), getScanSettings(), getScanCallback());
}
private List<ScanFilter> getScanFilters() {
List<ScanFilter> allFilters = new ArrayList<>();
for (DeviceCoordinator coordinator : DeviceHelper.getInstance().getAllCoordinators()) {
allFilters.addAll(coordinator.createBLEScanFilters());
}
return allFilters;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private ScanSettings getScanSettings() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return new ScanSettings.Builder()
.setScanMode(SCAN_MODE_LOW_LATENCY)
.setMatchMode(MATCH_MODE_STICKY)
.build();
} else {
return new ScanSettings.Builder()
.setScanMode(SCAN_MODE_LOW_LATENCY)
.build();
}
}
private void startBTLEDiscovery() {
LOG.info("Starting BTLE Discovery");
handler.removeMessages(0, stopRunnable);
@ -307,6 +554,12 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
adapter.startDiscovery();
}
private void checkAndRequestLocationPermission() {
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 0);
}
}
private Message getPostMessage(Runnable runnable) {
Message m = Message.obtain(handler, runnable);
m.obj = runnable;
@ -323,17 +576,35 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
stopDiscovery();
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(deviceCandidate);
LOG.info("Using device candidate " + deviceCandidate + " with coordinator: " + coordinator.getClass());
Class<? extends Activity> pairingActivity = coordinator.getPairingActivity();
if (pairingActivity != null) {
Intent intent = new Intent(this, pairingActivity);
intent.putExtra(DeviceCoordinator.EXTRA_DEVICE_MAC_ADDRESS, deviceCandidate.getMacAddress());
intent.putExtra(DeviceCoordinator.EXTRA_DEVICE_CANDIDATE, deviceCandidate);
startActivity(intent);
} else {
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
int bondingStyle = coordinator.getBondingStyle(device);
if (bondingStyle == DeviceCoordinator.BONDING_STYLE_NONE) {
LOG.info("No bonding needed, according to coordinator, so connecting right away");
connectAndFinish(device);
return;
}
try {
BluetoothDevice btDevice = adapter.getRemoteDevice(deviceCandidate.getMacAddress());
if (btDevice.createBond()) {
// async, wait for bonding event to finish this activity
bondingAddress = btDevice.getAddress();
switch (btDevice.getBondState()) {
case BluetoothDevice.BOND_NONE: {
createBond(deviceCandidate, bondingStyle);
break;
}
case BluetoothDevice.BOND_BONDING:
// async, wait for bonding event to finish this activity
bondingDevice = deviceCandidate;
break;
case BluetoothDevice.BOND_BONDED:
handleDeviceBonded();
break;
}
} catch (Exception e) {
LOG.error("Error pairing device: " + deviceCandidate.getMacAddress());

View File

@ -0,0 +1,392 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo, Uwe Hermann
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.Intent;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.NavUtils;
import android.util.Log;
import android.view.MenuItem;
import android.webkit.ConsoleMessage;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.Scanner;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class ExternalPebbleJSActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(ExternalPebbleJSActivity.class);
private UUID appUuid;
private Uri confUri;
private GBDevice mGBDevice = null;
private WebView myWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle extras = getIntent().getExtras();
if (extras != null) {
mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
appUuid = (UUID) extras.getSerializable(DeviceService.EXTRA_APP_UUID);
} else {
throw new IllegalArgumentException("Must provide a device when invoking this activity");
}
setContentView(R.layout.activity_external_pebble_js);
myWebView = (WebView) findViewById(R.id.configureWebview);
myWebView.clearCache(true);
myWebView.setWebViewClient(new GBWebClient());
myWebView.setWebChromeClient(new GBChromeClient());
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
//needed to access the DOM
webSettings.setDomStorageEnabled(true);
//needed for localstorage
webSettings.setDatabaseEnabled(true);
JSInterface gbJSInterface = new JSInterface(this);
myWebView.addJavascriptInterface(gbJSInterface, "GBjs");
myWebView.loadUrl("file:///android_asset/app_config/configure.html");
}
@Override
protected void onNewIntent(Intent incoming) {
incoming.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
super.onNewIntent(incoming);
confUri = incoming.getData();
}
@Override
protected void onResume() {
super.onResume();
String queryString = "";
if (confUri != null) {
//getting back with configuration data
try {
appUuid = UUID.fromString(confUri.getHost());
queryString = confUri.getEncodedQuery();
} catch (IllegalArgumentException e) {
GB.toast("returned uri: " + confUri.toString(), Toast.LENGTH_LONG, GB.ERROR);
}
myWebView.loadUrl("file:///android_asset/app_config/configure.html?" + queryString);
}
}
private JSONObject getAppConfigurationKeys() {
try {
File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
File configurationFile = new File(destDir, appUuid.toString() + ".json");
if (configurationFile.exists()) {
String jsonstring = FileUtils.getStringFromFile(configurationFile);
JSONObject json = new JSONObject(jsonstring);
return json.getJSONObject("appKeys");
}
} catch (IOException | JSONException e) {
e.printStackTrace();
}
return null;
}
private boolean isLocationEnabledForWatchApp() {
return true; //as long as we don't give watchapp internet access it's not a problem
}
private class GBChromeClient extends WebChromeClient {
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
if (ConsoleMessage.MessageLevel.ERROR.equals(consoleMessage.messageLevel())) {
GB.toast(consoleMessage.message(), Toast.LENGTH_LONG, GB.ERROR);
//TODO: show error page
}
return super.onConsoleMessage(consoleMessage);
}
}
private class GBWebClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("http://") || url.startsWith("https://")) {
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
} else {
url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json=");
view.loadUrl(url);
}
return true;
}
}
private class JSInterface {
Context mContext;
public JSInterface(Context c) {
mContext = c;
}
@JavascriptInterface
public void gbLog(String msg) {
Log.d("WEBVIEW", msg);
}
@JavascriptInterface
public void sendAppMessage(String msg) {
LOG.debug("from WEBVIEW: " + msg);
JSONObject knownKeys = getAppConfigurationKeys();
try {
JSONObject in = new JSONObject(msg);
JSONObject out = new JSONObject();
String inKey, outKey;
boolean passKey;
for (Iterator<String> key = in.keys(); key.hasNext(); ) {
passKey = false;
inKey = key.next();
outKey = null;
int pebbleAppIndex = knownKeys.optInt(inKey, -1);
if (pebbleAppIndex != -1) {
passKey = true;
outKey = String.valueOf(pebbleAppIndex);
} else {
//do not discard integer keys (see https://developer.pebble.com/guides/communication/using-pebblekit-js/ )
Scanner scanner = new Scanner(inKey);
if (scanner.hasNextInt() && inKey.equals("" + scanner.nextInt())) {
passKey = true;
outKey = inKey;
}
}
if (passKey) {
Object obj = in.get(inKey);
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);
}
}
LOG.info(out.toString());
GBApplication.deviceService().onAppConfiguration(appUuid, out.toString());
} catch (JSONException e) {
e.printStackTrace();
}
}
@JavascriptInterface
public String getActiveWatchInfo() {
JSONObject wi = new JSONObject();
try {
wi.put("firmware", mGBDevice.getFirmwareVersion());
wi.put("platform", PebbleUtils.getPlatformName(mGBDevice.getModel()));
wi.put("model", PebbleUtils.getModel(mGBDevice.getModel()));
//TODO: use real info
wi.put("language", "en");
} catch (JSONException e) {
e.printStackTrace();
}
//Json not supported apparently, we need to cast back and forth
return wi.toString();
}
@JavascriptInterface
public String getAppConfigurationFile() {
try {
File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
File configurationFile = new File(destDir, appUuid.toString() + "_config.js");
if (configurationFile.exists()) {
return "file:///" + configurationFile.getAbsolutePath();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@JavascriptInterface
public String getAppStoredPreset() {
try {
File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
File configurationFile = new File(destDir, appUuid.toString() + "_preset.json");
if (configurationFile.exists()) {
return FileUtils.getStringFromFile(configurationFile);
}
} catch (IOException e) {
GB.toast("Error reading presets", Toast.LENGTH_LONG, GB.ERROR);
e.printStackTrace();
}
return null;
}
@JavascriptInterface
public void saveAppStoredPreset(String msg) {
Writer writer;
try {
File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
File presetsFile = new File(destDir, appUuid.toString() + "_preset.json");
writer = new BufferedWriter(new FileWriter(presetsFile));
writer.write(msg);
writer.close();
GB.toast("Presets stored", Toast.LENGTH_SHORT, GB.INFO);
} catch (IOException e) {
GB.toast("Error storing presets", Toast.LENGTH_LONG, GB.ERROR);
e.printStackTrace();
}
}
@JavascriptInterface
public String getAppUUID() {
return appUuid.toString();
}
@JavascriptInterface
public String getAppLocalstoragePrefix() {
String prefix = mGBDevice.getAddress() + appUuid.toString();
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
byte[] bytes = prefix.getBytes("UTF-8");
digest.update(bytes, 0, bytes.length);
bytes = digest.digest();
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02X", bytes[i]));
}
return sb.toString().toLowerCase();
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
e.printStackTrace();
return prefix;
}
}
@JavascriptInterface
public String getWatchToken() {
//specification says: A string that is guaranteed to be identical for each Pebble device for the same app across different mobile devices. The token is unique to your app and cannot be used to track Pebble devices across applications. see https://developer.pebble.com/docs/js/Pebble/
return "gb" + appUuid.toString();
}
@JavascriptInterface
public void closeActivity() {
NavUtils.navigateUpFromSameTask((ExternalPebbleJSActivity) mContext);
}
@JavascriptInterface
public String getCurrentPosition() {
if (!isLocationEnabledForWatchApp()) {
return "";
}
//we need to override this because the coarse location is not enough for the android webview, we should add the permission for fine location.
JSONObject geoPosition = new JSONObject();
JSONObject coords = new JSONObject();
try {
Prefs prefs = GBApplication.getPrefs();
geoPosition.put("timestamp", (System.currentTimeMillis() / 1000) - 86400); //let JS know this value is really old
coords.put("latitude", prefs.getFloat("location_latitude", 0));
coords.put("longitude", prefs.getFloat("location_longitude", 0));
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
prefs.getBoolean("use_updated_location_if_available", false)) {
LocationManager locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
String provider = locationManager.getBestProvider(criteria, false);
if (provider != null) {
Location lastKnownLocation = locationManager.getLastKnownLocation(provider);
if (lastKnownLocation != null) {
geoPosition.put("timestamp", lastKnownLocation.getTime());
coords.put("latitude", (float) lastKnownLocation.getLatitude());
coords.put("longitude", (float) lastKnownLocation.getLongitude());
coords.put("accuracy", lastKnownLocation.getAccuracy());
coords.put("altitude", lastKnownLocation.getAltitude());
coords.put("speed", lastKnownLocation.getSpeed());
}
}
}
geoPosition.put("coords", coords);
} catch (JSONException e) {
e.printStackTrace();
}
return geoPosition.toString();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -1,6 +1,22 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@ -29,14 +45,16 @@ import nodomain.freeyourgadget.gadgetbridge.adapter.ItemWithDetailsAdapter;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class FwAppInstallerActivity extends Activity implements InstallActivity {
public class FwAppInstallerActivity extends AbstractGBActivity implements InstallActivity {
private static final Logger LOG = LoggerFactory.getLogger(FwAppInstallerActivity.class);
private static final String ITEM_DETAILS = "details";
private TextView fwAppInstallTextView;
private Button installButton;
@ -45,13 +63,20 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
private InstallHandler installHandler;
private boolean mayConnect;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
private ProgressBar mProgressBar;
private ListView itemListView;
private final List<ItemWithDetails> mItems = new ArrayList<>();
private ItemWithDetailsAdapter mItemAdapter;
private ListView detailsListView;
private ItemWithDetailsAdapter mDetailsItemAdapter;
private ArrayList<ItemWithDetails> mDetails = new ArrayList<>();
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ControlCenter.ACTION_QUIT)) {
finish();
} else if (action.equals(GBDevice.ACTION_DEVICE_CHANGED)) {
if (GBDevice.ACTION_DEVICE_CHANGED.equals(action)) {
device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
if (device != null) {
refreshBusyState(device);
@ -67,13 +92,13 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
validateInstallation();
}
}
} else if (GB.ACTION_DISPLAY_MESSAGE.equals(action)) {
String message = intent.getStringExtra(GB.DISPLAY_MESSAGE_MESSAGE);
int severity = intent.getIntExtra(GB.DISPLAY_MESSAGE_SEVERITY, GB.INFO);
addMessage(message, severity);
}
}
};
private ProgressBar mProgressBar;
private ListView itemListView;
private List<ItemWithDetails> mItems = new ArrayList<>();
private ItemWithDetailsAdapter mItemAdapter;
private void refreshBusyState(GBDevice dev) {
if (dev.isConnecting() || dev.isBusy()) {
@ -89,7 +114,7 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
private void connect() {
mayConnect = false; // only do that once per #onCreate
GBApplication.deviceService().connect(device != null ? device.getAddress() : null);
GBApplication.deviceService().connect(device);
}
private void validateInstallation() {
@ -102,11 +127,18 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_appinstaller);
getActionBar().setDisplayHomeAsUpEnabled(true);
GBDevice dev = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
if (dev != null) {
device = dev;
}
if (savedInstanceState != null) {
mDetails = savedInstanceState.getParcelableArrayList(ITEM_DETAILS);
if (mDetails == null) {
mDetails = new ArrayList<>();
}
}
mayConnect = true;
itemListView = (ListView) findViewById(R.id.itemListView);
mItemAdapter = new ItemWithDetailsAdapter(this, mItems);
@ -114,10 +146,14 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
fwAppInstallTextView = (TextView) findViewById(R.id.infoTextView);
installButton = (Button) findViewById(R.id.installButton);
mProgressBar = (ProgressBar) findViewById(R.id.installProgressBar);
detailsListView = (ListView) findViewById(R.id.detailsListView);
mDetailsItemAdapter = new ItemWithDetailsAdapter(this, mDetails);
mDetailsItemAdapter.setSize(ItemWithDetailsAdapter.SIZE_SMALL);
detailsListView.setAdapter(mDetailsItemAdapter);
setInstallEnabled(false);
IntentFilter filter = new IntentFilter();
filter.addAction(ControlCenter.ACTION_QUIT);
filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
filter.addAction(GB.ACTION_DISPLAY_MESSAGE);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
installButton.setOnClickListener(new View.OnClickListener() {
@ -130,6 +166,9 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
});
uri = getIntent().getData();
if (uri == null) { //for "share" intent
uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
}
installHandler = findInstallHandlerFor(uri);
if (installHandler == null) {
setInfoText(getString(R.string.installer_activity_unable_to_find_handler));
@ -145,6 +184,12 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList(ITEM_DETAILS, mDetails);
}
private InstallHandler findInstallHandlerFor(Uri uri) {
for (DeviceCoordinator coordinator : DeviceHelper.getInstance().getAllCoordinators()) {
InstallHandler handler = coordinator.findInstallHandler(uri, this);
@ -176,6 +221,11 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
fwAppInstallTextView.setText(text);
}
@Override
public CharSequence getInfoText() {
return fwAppInstallTextView.getText();
}
@Override
public void setInstallEnabled(boolean enable) {
boolean enabled = device != null && device.isConnected() && enable;
@ -195,4 +245,9 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
mItems.add(item);
mItemAdapter.notifyDataSetChanged();
}
private void addMessage(String message, int severity) {
mDetails.add(new GenericItem(message));
mDetailsItemAdapter.notifyDataSetChanged();
}
}

View File

@ -0,0 +1,25 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import java.util.Locale;
public interface GBActivity {
void setLanguage(Locale language, boolean invalidateLanguage);
void setTheme(int themeId);
}

View File

@ -0,0 +1,30 @@
/* Copyright (C) 2016-2017 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.activities;
public class HeartRateUtils {
public static final int MAX_HEART_RATE_VALUE = 250;
public static final int MIN_HEART_RATE_VALUE = 0;
/**
* The maxiumum gap between two hr measurements in which
* we interpolate between the measurements. Otherwise, two
* distinct measurements will be shown.
*
* Value is in minutes
*/
public static final int MAX_HR_MEASUREMENTS_GAP_MINUTES = 10;
}

View File

@ -1,8 +1,26 @@
/* 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.activities;
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
public interface InstallActivity {
CharSequence getInfoText();
void setInfoText(String text);
void setInstallEnabled(boolean enable);
@ -10,4 +28,5 @@ public interface InstallActivity {
void clearInstallItems();
void setInstallItem(ItemWithDetails item);
}

View File

@ -1,19 +1,68 @@
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti, Normano64
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.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_HEIGHT_CM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_SLEEP_DURATION;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_STEPS_GOAL;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_WEIGHT_KG;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_YEAR_OF_BIRTH;
public class SettingsActivity extends AbstractSettingsActivity {
private static final Logger LOG = LoggerFactory.getLogger(SettingsActivity.class);
public static final String PREF_MEASUREMENT_SYSTEM = "measurement_system";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -50,11 +99,20 @@ public class SettingsActivity extends AbstractSettingsActivity {
}
});
final Preference pebbleEmuAddr = findPreference("pebble_emu_addr");
pebbleEmuAddr.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
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
public boolean onPreferenceChange(Preference preference, Object newVal) {
Intent refreshIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST);
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(refreshIntent);
preference.setSummary(newVal.toString());
return true;
@ -62,11 +120,11 @@ public class SettingsActivity extends AbstractSettingsActivity {
});
final Preference pebbleEmuPort = findPreference("pebble_emu_port");
pebbleEmuPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
pref = findPreference("pebble_emu_port");
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
Intent refreshIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST);
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(refreshIntent);
preference.setSummary(newVal.toString());
return true;
@ -74,6 +132,133 @@ public class SettingsActivity extends AbstractSettingsActivity {
});
pref = findPreference("log_to_file");
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
boolean doEnable = Boolean.TRUE.equals(newVal);
try {
if (doEnable) {
FileUtils.getExternalFilesDir(); // ensures that it is created
}
GBApplication.setupLogging(doEnable);
} catch (IOException ex) {
GB.toast(getApplicationContext(),
getString(R.string.error_creating_directory_for_logfiles, ex.getLocalizedMessage()),
Toast.LENGTH_LONG,
GB.ERROR,
ex);
}
return true;
}
});
pref = findPreference("language");
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
String newLang = newVal.toString();
try {
GBApplication.setLanguage(newLang);
recreate();
} catch (Exception ex) {
GB.toast(getApplicationContext(),
"Error setting language: " + ex.getLocalizedMessage(),
Toast.LENGTH_LONG,
GB.ERROR,
ex);
}
return true;
}
});
final Preference unit = findPreference(PREF_MEASUREMENT_SYSTEM);
unit.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
invokeLater(new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSendConfiguration(PREF_MEASUREMENT_SYSTEM);
}
});
preference.setSummary(newVal.toString());
return true;
}
});
if (!GBApplication.isRunningMarshmallowOrLater()) {
pref = findPreference("notification_filter");
PreferenceCategory category = (PreferenceCategory) findPreference("pref_key_notifications");
category.removePreference(pref);
}
pref = findPreference("location_aquire");
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(SettingsActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 0);
}
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
String provider = locationManager.getBestProvider(criteria, false);
if (provider != null) {
Location location = locationManager.getLastKnownLocation(provider);
if (location != null) {
setLocationPreferences(location);
} else {
locationManager.requestSingleUpdate(provider, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
setLocationPreferences(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
LOG.info("provider status changed to " + status + " (" + provider + ")");
}
@Override
public void onProviderEnabled(String provider) {
LOG.info("provider enabled (" + provider + ")");
}
@Override
public void onProviderDisabled(String provider) {
LOG.info("provider disabled (" + provider + ")");
GB.toast(SettingsActivity.this, getString(R.string.toast_enable_networklocationprovider), 3000, 0);
}
}, null);
}
} else {
LOG.warn("No location provider found, did you deny location permission?");
}
return true;
}
});
pref = findPreference("canned_messages_dismisscall_send");
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Prefs prefs = GBApplication.getPrefs();
ArrayList<String> messages = new ArrayList<>();
for (int i = 1; i <= 16; i++) {
String message = prefs.getString("canned_message_dismisscall_" + i, null);
if (message != null && !message.equals("")) {
messages.add(message);
}
}
CannedMessagesSpec cannedMessagesSpec = new CannedMessagesSpec();
cannedMessagesSpec.type = CannedMessagesSpec.TYPE_MISSEDCALLS;
cannedMessagesSpec.cannedMessages = messages.toArray(new String[messages.size()]);
GBApplication.deviceService().onSetCannedMessages(cannedMessagesSpec);
return true;
}
});
// Get all receivers of Media Buttons
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
@ -100,17 +285,72 @@ public class SettingsActivity extends AbstractSettingsActivity {
audioPlayer.setDefaultValue(newValues[0]);
}
/*
* delayed execution so that the preferences are applied first
*/
private void invokeLater(Runnable runnable) {
getListView().post(runnable);
}
@Override
protected String[] getPreferenceKeysWithSummary() {
return new String[]{
"audio_player",
"notification_mode_calls",
"notification_mode_sms",
"notification_mode_k9mail",
"pebble_emu_addr",
"pebble_emu_port",
"pebble_reconnect_attempts",
"location_latitude",
"location_longitude",
"canned_reply_suffix",
"canned_reply_1",
"canned_reply_2",
"canned_reply_3",
"canned_reply_4",
"canned_reply_5",
"canned_reply_6",
"canned_reply_7",
"canned_reply_8",
"canned_reply_9",
"canned_reply_10",
"canned_reply_11",
"canned_reply_12",
"canned_reply_13",
"canned_reply_14",
"canned_reply_15",
"canned_reply_16",
"canned_message_dismisscall_1",
"canned_message_dismisscall_2",
"canned_message_dismisscall_3",
"canned_message_dismisscall_4",
"canned_message_dismisscall_5",
"canned_message_dismisscall_6",
"canned_message_dismisscall_7",
"canned_message_dismisscall_8",
"canned_message_dismisscall_9",
"canned_message_dismisscall_10",
"canned_message_dismisscall_11",
"canned_message_dismisscall_12",
"canned_message_dismisscall_13",
"canned_message_dismisscall_14",
"canned_message_dismisscall_15",
"canned_message_dismisscall_16",
PREF_USER_YEAR_OF_BIRTH,
PREF_USER_HEIGHT_CM,
PREF_USER_WEIGHT_KG,
PREF_USER_SLEEP_DURATION,
PREF_USER_STEPS_GOAL,
};
}
private void setLocationPreferences(Location location) {
String latitude = String.format(Locale.US, "%.6g", location.getLatitude());
String longitude = String.format(Locale.US, "%.6g", location.getLongitude());
LOG.info("got location. Lat: " + latitude + " Lng: " + longitude);
GB.toast(SettingsActivity.this, getString(R.string.toast_aqurired_networklocation), 2000, 0);
EditTextPreference pref_latitude = (EditTextPreference) findPreference("location_latitude");
EditTextPreference pref_longitude = (EditTextPreference) findPreference("location_longitude");
pref_latitude.setText(latitude);
pref_longitude.setText(longitude);
pref_latitude.setSummary(latitude);
pref_longitude.setSummary(longitude);
}
}

View File

@ -0,0 +1,59 @@
/* 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.activities;
import android.os.Bundle;
import android.widget.SeekBar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
public class VibrationActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(VibrationActivity.class);
private SeekBar seekBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vibration);
seekBar = (SeekBar) findViewById(R.id.vibration_seekbar);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (progress > 0) { // 1-16
progress = progress * 16 - 1; // max 255
}
GBApplication.deviceService().onSetConstantVibration(progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
}

View File

@ -0,0 +1,487 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupMenu;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
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.ExternalPebbleJSActivity;
import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAppAdapter;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
public abstract class AbstractAppManagerFragment extends Fragment {
public static final String ACTION_REFRESH_APPLIST
= "nodomain.freeyourgadget.gadgetbridge.appmanager.action.refresh_applist";
private static final Logger LOG = LoggerFactory.getLogger(AbstractAppManagerFragment.class);
private ItemTouchHelper appManagementTouchHelper;
protected abstract List<GBDeviceApp> getSystemAppsInCategory();
protected abstract String getSortFilename();
protected abstract boolean isCacheManager();
protected abstract boolean filterApp(GBDeviceApp gbDeviceApp);
public void startDragging(RecyclerView.ViewHolder viewHolder) {
appManagementTouchHelper.startDrag(viewHolder);
}
protected void onChangedAppOrder() {
List<UUID> uuidList = new ArrayList<>();
for (GBDeviceApp gbDeviceApp : mGBDeviceAppAdapter.getAppList()) {
uuidList.add(gbDeviceApp.getUUID());
}
AppManagerActivity.rewriteAppOrderFile(getSortFilename(), uuidList);
}
protected void refreshList() {
appList.clear();
ArrayList uuids = AppManagerActivity.getUuidsFromFile(getSortFilename());
List<GBDeviceApp> systemApps = getSystemAppsInCategory();
boolean needsRewrite = false;
for (GBDeviceApp systemApp : systemApps) {
if (!uuids.contains(systemApp.getUUID())) {
uuids.add(systemApp.getUUID());
needsRewrite = true;
}
}
if (needsRewrite) {
AppManagerActivity.rewriteAppOrderFile(getSortFilename(), uuids);
}
appList.addAll(getCachedApps(uuids));
}
private void refreshListFromPebble(Intent intent) {
appList.clear();
int appCount = intent.getIntExtra("app_count", 0);
for (Integer i = 0; i < appCount; i++) {
String appName = intent.getStringExtra("app_name" + i.toString());
String appCreator = intent.getStringExtra("app_creator" + i.toString());
UUID uuid = UUID.fromString(intent.getStringExtra("app_uuid" + i.toString()));
GBDeviceApp.Type appType = GBDeviceApp.Type.values()[intent.getIntExtra("app_type" + i.toString(), 0)];
GBDeviceApp app = new GBDeviceApp(uuid, appName, appCreator, "", appType);
app.setOnDevice(true);
if (filterApp(app)) {
appList.add(app);
}
}
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ACTION_REFRESH_APPLIST)) {
if (intent.hasExtra("app_count")) {
LOG.info("got app info from pebble");
if (!isCacheManager()) {
LOG.info("will refresh list based on data from pebble");
refreshListFromPebble(intent);
}
} else if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 3 || isCacheManager()) {
refreshList();
}
mGBDeviceAppAdapter.notifyDataSetChanged();
}
}
};
protected final List<GBDeviceApp> appList = new ArrayList<>();
private GBDeviceAppAdapter mGBDeviceAppAdapter;
protected GBDevice mGBDevice = null;
protected List<GBDeviceApp> getCachedApps(List<UUID> uuids) {
List<GBDeviceApp> cachedAppList = new ArrayList<>();
File cachePath;
try {
cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache");
} catch (IOException e) {
LOG.warn("could not get external dir while reading pbw cache.");
return cachedAppList;
}
File[] files;
if (uuids == null) {
files = cachePath.listFiles();
} else {
files = new File[uuids.size()];
int index = 0;
for (UUID uuid : uuids) {
files[index++] = new File(uuid.toString() + ".pbw");
}
}
if (files != null) {
for (File file : files) {
if (file.getName().endsWith(".pbw")) {
String baseName = file.getName().substring(0, file.getName().length() - 4);
//metadata
File jsonFile = new File(cachePath, baseName + ".json");
//configuration
File configFile = new File(cachePath, baseName + "_config.js");
try {
String jsonstring = FileUtils.getStringFromFile(jsonFile);
JSONObject json = new JSONObject(jsonstring);
cachedAppList.add(new GBDeviceApp(json, configFile.exists()));
} catch (Exception e) {
LOG.info("could not read json file for " + baseName);
//FIXME: this is really ugly, if we do not find system uuids in pbw cache add them manually. Also duplicated code
switch (baseName) {
case "8f3c8686-31a1-4f5f-91f5-01600c9bdc59":
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Tic Toc (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
break;
case "1f03293d-47af-4f28-b960-f2b02a6dd757":
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Music (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
break;
case "b2cae818-10f8-46df-ad2b-98ad2254a3c1":
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Notifications (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
break;
case "67a32d95-ef69-46d4-a0b9-854cc62f97f9":
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Alarms (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
break;
case "18e443ce-38fd-47c8-84d5-6d0c775fbe55":
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Watchfaces (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
break;
case "0863fc6a-66c5-4f62-ab8a-82ed00a98b5d":
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Send Text (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
break;
}
/*
else if (baseName.equals("4dab81a6-d2fc-458a-992c-7a1f3b96a970")) {
cachedAppList.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
} else if (baseName.equals("cf1e816a-9db0-4511-bbb8-f60c48ca8fac")) {
cachedAppList.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
}
*/
if (mGBDevice != null) {
if (PebbleUtils.hasHealth(mGBDevice.getModel())) {
if (baseName.equals(PebbleProtocol.UUID_PEBBLE_HEALTH.toString())) {
cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
continue;
}
}
if (PebbleUtils.hasHRM(mGBDevice.getModel())) {
if (baseName.equals(PebbleProtocol.UUID_WORKOUT.toString())) {
cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_WORKOUT, "Workout (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
continue;
}
}
if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 4) {
if (baseName.equals("3af858c3-16cb-4561-91e7-f1ad2df8725f")) {
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Kickstart (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
}
if (baseName.equals(PebbleProtocol.UUID_WEATHER.toString())) {
cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_WEATHER, "Weather (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
}
}
}
if (uuids == null) {
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), baseName, "N/A", "", GBDeviceApp.Type.UNKNOWN));
}
}
}
}
}
return cachedAppList;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mGBDevice = ((AppManagerActivity) getActivity()).getGBDevice();
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_REFRESH_APPLIST);
LocalBroadcastManager.getInstance(getContext()).registerReceiver(mReceiver, filter);
if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) < 3) {
GBApplication.deviceService().onAppInfoReq();
if (isCacheManager()) {
refreshList();
}
} else {
refreshList();
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final FloatingActionButton appListFab = ((FloatingActionButton) getActivity().findViewById(R.id.fab));
View rootView = inflater.inflate(R.layout.activity_appmanager, container, false);
RecyclerView appListView = (RecyclerView) (rootView.findViewById(R.id.appListView));
appListView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dy > 0) {
appListFab.hide();
} else if (dy < 0) {
appListFab.show();
}
}
});
appListView.setLayoutManager(new LinearLayoutManager(getActivity()));
mGBDeviceAppAdapter = new GBDeviceAppAdapter(appList, R.layout.item_pebble_watchapp, this);
appListView.setAdapter(mGBDeviceAppAdapter);
ItemTouchHelper.Callback appItemTouchHelperCallback = new AppItemTouchHelperCallback(mGBDeviceAppAdapter);
appManagementTouchHelper = new ItemTouchHelper(appItemTouchHelperCallback);
appManagementTouchHelper.attachToRecyclerView(appListView);
return rootView;
}
protected void sendOrderToDevice(String concatFilename) {
ArrayList<UUID> uuids = new ArrayList<>();
for (GBDeviceApp gbDeviceApp : mGBDeviceAppAdapter.getAppList()) {
uuids.add(gbDeviceApp.getUUID());
}
if (concatFilename != null) {
ArrayList<UUID> concatUuids = AppManagerActivity.getUuidsFromFile(concatFilename);
uuids.addAll(concatUuids);
}
GBApplication.deviceService().onAppReorder(uuids.toArray(new UUID[uuids.size()]));
}
public boolean openPopupMenu(View view, GBDeviceApp deviceApp) {
PopupMenu popupMenu = new PopupMenu(getContext(), view);
popupMenu.getMenuInflater().inflate(R.menu.appmanager_context, popupMenu.getMenu());
Menu menu = popupMenu.getMenu();
final GBDeviceApp selectedApp = deviceApp;
if (!selectedApp.isInCache()) {
menu.removeItem(R.id.appmanager_app_reinstall);
menu.removeItem(R.id.appmanager_app_delete_cache);
}
if (!PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) {
menu.removeItem(R.id.appmanager_health_activate);
menu.removeItem(R.id.appmanager_health_deactivate);
}
if (!PebbleProtocol.UUID_WORKOUT.equals(selectedApp.getUUID())) {
menu.removeItem(R.id.appmanager_hrm_activate);
menu.removeItem(R.id.appmanager_hrm_deactivate);
}
if (!PebbleProtocol.UUID_WEATHER.equals(selectedApp.getUUID())) {
menu.removeItem(R.id.appmanager_weather_activate);
menu.removeItem(R.id.appmanager_weather_deactivate);
menu.removeItem(R.id.appmanager_weather_install_provider);
}
if (selectedApp.getType() == GBDeviceApp.Type.APP_SYSTEM || selectedApp.getType() == GBDeviceApp.Type.WATCHFACE_SYSTEM) {
menu.removeItem(R.id.appmanager_app_delete);
}
if (!selectedApp.isConfigurable()) {
menu.removeItem(R.id.appmanager_app_configure);
}
if (PebbleProtocol.UUID_WEATHER.equals(selectedApp.getUUID())) {
PackageManager pm = getActivity().getPackageManager();
try {
pm.getPackageInfo("ru.gelin.android.weather.notification", PackageManager.GET_ACTIVITIES);
menu.removeItem(R.id.appmanager_weather_install_provider);
} catch (PackageManager.NameNotFoundException e) {
menu.removeItem(R.id.appmanager_weather_activate);
menu.removeItem(R.id.appmanager_weather_deactivate);
}
}
switch (selectedApp.getType()) {
case WATCHFACE:
case APP_GENERIC:
case APP_ACTIVITYTRACKER:
break;
default:
menu.removeItem(R.id.appmanager_app_openinstore);
}
//menu.setHeaderTitle(selectedApp.getName());
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
return onContextItemSelected(item, selectedApp);
}
}
);
popupMenu.show();
return true;
}
private boolean onContextItemSelected(MenuItem item, GBDeviceApp selectedApp) {
switch (item.getItemId()) {
case R.id.appmanager_app_delete_cache:
String baseName;
try {
baseName = FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID();
} catch (IOException e) {
LOG.warn("could not get external dir while trying to access pbw cache.");
return true;
}
String[] suffixToDelete = new String[]{".pbw", ".json", "_config.js", "_preset.json"};
for (String suffix : suffixToDelete) {
File fileToDelete = new File(baseName + suffix);
if (!fileToDelete.delete()) {
LOG.warn("could not delete file from pbw cache: " + fileToDelete.toString());
} else {
LOG.info("deleted file: " + fileToDelete.toString());
}
}
AppManagerActivity.deleteFromAppOrderFile("pbwcacheorder.txt", selectedApp.getUUID()); // FIXME: only if successful
// fall through
case R.id.appmanager_app_delete:
if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 3) {
AppManagerActivity.deleteFromAppOrderFile(mGBDevice.getAddress() + ".watchapps", selectedApp.getUUID()); // FIXME: only if successful
AppManagerActivity.deleteFromAppOrderFile(mGBDevice.getAddress() + ".watchfaces", selectedApp.getUUID()); // FIXME: only if successful
Intent refreshIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(refreshIntent);
}
GBApplication.deviceService().onAppDelete(selectedApp.getUUID());
return true;
case R.id.appmanager_app_reinstall:
File cachePath;
try {
cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID() + ".pbw");
} catch (IOException e) {
LOG.warn("could not get external dir while trying to access pbw cache.");
return true;
}
GBApplication.deviceService().onInstallApp(Uri.fromFile(cachePath));
return true;
case R.id.appmanager_health_activate:
GBApplication.deviceService().onInstallApp(Uri.parse("fake://health"));
return true;
case R.id.appmanager_hrm_activate:
GBApplication.deviceService().onInstallApp(Uri.parse("fake://hrm"));
return true;
case R.id.appmanager_weather_activate:
GBApplication.deviceService().onInstallApp(Uri.parse("fake://weather"));
return true;
case R.id.appmanager_health_deactivate:
case R.id.appmanager_hrm_deactivate:
case R.id.appmanager_weather_deactivate:
GBApplication.deviceService().onAppDelete(selectedApp.getUUID());
return true;
case R.id.appmanager_weather_install_provider:
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://f-droid.org/app/ru.gelin.android.weather.notification")));
return true;
case R.id.appmanager_app_configure:
GBApplication.deviceService().onAppStart(selectedApp.getUUID(), true);
Intent startIntent = new Intent(getContext().getApplicationContext(), ExternalPebbleJSActivity.class);
startIntent.putExtra(DeviceService.EXTRA_APP_UUID, selectedApp.getUUID());
startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice);
startActivity(startIntent);
return true;
case R.id.appmanager_app_openinstore:
String url = "https://apps.getpebble.com/en_US/search/" + ((selectedApp.getType() == GBDeviceApp.Type.WATCHFACE) ? "watchfaces" : "watchapps") + "/1?query=" + selectedApp.getName() + "&dev_settings=true";
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
startActivity(intent);
return true;
default:
return super.onContextItemSelected(item);
}
}
@Override
public void onDestroy() {
LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mReceiver);
super.onDestroy();
}
public class AppItemTouchHelperCallback extends ItemTouchHelper.Callback {
private final GBDeviceAppAdapter gbDeviceAppAdapter;
public AppItemTouchHelperCallback(GBDeviceAppAdapter gbDeviceAppAdapter) {
this.gbDeviceAppAdapter = gbDeviceAppAdapter;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//app reordering is not possible on old firmwares
if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) < 3 && !isCacheManager()) {
return 0;
}
//we only support up and down movement and only for moving, not for swiping apps away
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
gbDeviceAppAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
//nothing to do
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
onChangedAppOrder();
}
}
}

View File

@ -0,0 +1,198 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
import android.app.Activity;
import android.content.Intent;
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.view.ViewPager;
import android.view.MenuItem;
import android.view.View;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractFragmentPagerAdapter;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragmentActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.FwAppInstallerActivity;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
public class AppManagerActivity extends AbstractGBFragmentActivity {
private static final Logger LOG = LoggerFactory.getLogger(AbstractAppManagerFragment.class);
private int READ_REQUEST_CODE = 42;
private GBDevice mGBDevice = null;
public GBDevice getGBDevice() {
return mGBDevice;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragmentappmanager);
Bundle extras = getIntent().getExtras();
if (extras != null) {
mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
} else {
throw new IllegalArgumentException("Must provide a device when invoking this activity");
}
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
assert fab != null;
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, READ_REQUEST_CODE);
}
});
// Set up the ViewPager with the sections adapter.
ViewPager viewPager = (ViewPager) findViewById(R.id.appmanager_pager);
if (viewPager != null) {
viewPager.setAdapter(getPagerAdapter());
}
}
@Override
protected AbstractFragmentPagerAdapter createFragmentPagerAdapter(FragmentManager fragmentManager) {
return new SectionsPagerAdapter(fragmentManager);
}
public static synchronized void deleteFromAppOrderFile(String filename, UUID uuid) {
ArrayList<UUID> uuids = getUuidsFromFile(filename);
uuids.remove(uuid);
rewriteAppOrderFile(filename, uuids);
}
public class SectionsPagerAdapter extends AbstractFragmentPagerAdapter {
SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
switch (position) {
case 0:
return new AppManagerFragmentCache();
case 1:
return new AppManagerFragmentInstalledApps();
case 2:
return new AppManagerFragmentInstalledWatchfaces();
}
return null;
}
@Override
public int getCount() {
return 3;
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.appmanager_cached_watchapps_watchfaces);
case 1:
return getString(R.string.appmanager_installed_watchapps);
case 2:
return getString(R.string.appmanager_installed_watchfaces);
case 3:
}
return super.getPageTitle(position);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
static synchronized void rewriteAppOrderFile(String filename, List<UUID> uuids) {
try (BufferedWriter out = new BufferedWriter(new FileWriter(FileUtils.getExternalFilesDir() + "/" + filename))) {
for (UUID uuid : uuids) {
out.write(uuid.toString());
out.newLine();
}
} catch (IOException e) {
LOG.warn("can't write app order to file!");
}
}
synchronized public static void addToAppOrderFile(String filename, UUID uuid) {
ArrayList<UUID> uuids = getUuidsFromFile(filename);
if (!uuids.contains(uuid)) {
uuids.add(uuid);
rewriteAppOrderFile(filename, uuids);
}
}
static synchronized ArrayList<UUID> getUuidsFromFile(String filename) {
ArrayList<UUID> uuids = new ArrayList<>();
try (BufferedReader in = new BufferedReader(new FileReader(FileUtils.getExternalFilesDir() + "/" + filename))) {
String line;
while ((line = in.readLine()) != null) {
uuids.add(UUID.fromString(line));
}
} catch (IOException e) {
LOG.warn("could not read sort file");
}
return uuids;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
Intent startIntent = new Intent(AppManagerActivity.this, FwAppInstallerActivity.class);
startIntent.setAction(Intent.ACTION_VIEW);
startIntent.setDataAndType(resultData.getData(), null);
startActivity(startIntent);
}
}
}

View File

@ -0,0 +1,49 @@
/* Copyright (C) 2016-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.activities.appmanager;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
public class AppManagerFragmentCache extends AbstractAppManagerFragment {
@Override
public void refreshList() {
appList.clear();
appList.addAll(getCachedApps(null));
}
@Override
protected boolean isCacheManager() {
return true;
}
@Override
protected List<GBDeviceApp> getSystemAppsInCategory() {
return null;
}
@Override
public String getSortFilename() {
return "pbwcacheorder.txt";
}
@Override
protected boolean filterApp(GBDeviceApp gbDeviceApp) {
return true;
}
}

View File

@ -0,0 +1,75 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, 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.appmanager;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
public class AppManagerFragmentInstalledApps extends AbstractAppManagerFragment {
@Override
protected List<GBDeviceApp> getSystemAppsInCategory() {
List<GBDeviceApp> systemApps = new ArrayList<>();
//systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
//systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(UUID.fromString("1f03293d-47af-4f28-b960-f2b02a6dd757"), "Music (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_NOTIFICATIONS, "Notifications (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(UUID.fromString("67a32d95-ef69-46d4-a0b9-854cc62f97f9"), "Alarms (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(UUID.fromString("18e443ce-38fd-47c8-84d5-6d0c775fbe55"), "Watchfaces (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
if (mGBDevice != null) {
if (PebbleUtils.hasHealth(mGBDevice.getModel())) {
systemApps.add(new GBDeviceApp(UUID.fromString("0863fc6a-66c5-4f62-ab8a-82ed00a98b5d"), "Send Text (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
}
if (PebbleUtils.hasHRM(mGBDevice.getModel())) {
systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_WORKOUT, "Workout (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
}
if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 4) {
systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_WEATHER, "Weather (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
}
}
return systemApps;
}
@Override
protected boolean isCacheManager() {
return false;
}
@Override
protected String getSortFilename() {
return mGBDevice.getAddress() + ".watchapps";
}
@Override
protected void onChangedAppOrder() {
super.onChangedAppOrder();
sendOrderToDevice(mGBDevice.getAddress() + ".watchfaces");
}
@Override
protected boolean filterApp(GBDeviceApp gbDeviceApp) {
return gbDeviceApp.getType() == GBDeviceApp.Type.APP_ACTIVITYTRACKER || gbDeviceApp.getType() == GBDeviceApp.Type.APP_GENERIC;
}
}

View File

@ -0,0 +1,58 @@
/* Copyright (C) 2016-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.activities.appmanager;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
public class AppManagerFragmentInstalledWatchfaces extends AbstractAppManagerFragment {
@Override
protected List<GBDeviceApp> getSystemAppsInCategory() {
List<GBDeviceApp> systemWatchfaces = new ArrayList<>();
systemWatchfaces.add(new GBDeviceApp(UUID.fromString("8f3c8686-31a1-4f5f-91f5-01600c9bdc59"), "Tic Toc (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
systemWatchfaces.add(new GBDeviceApp(UUID.fromString("3af858c3-16cb-4561-91e7-f1ad2df8725f"), "Kickstart (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
return systemWatchfaces;
}
@Override
protected boolean isCacheManager() {
return false;
}
@Override
protected String getSortFilename() {
return mGBDevice.getAddress() + ".watchfaces";
}
@Override
protected void onChangedAppOrder() {
super.onChangedAppOrder();
sendOrderToDevice(mGBDevice.getAddress() + ".watchapps");
}
@Override
protected boolean filterApp(GBDeviceApp gbDeviceApp) {
if (gbDeviceApp.getType() == GBDeviceApp.Type.WATCHFACE) {
return true;
}
return false;
}
}

View File

@ -1,20 +1,50 @@
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti, walkjivefly
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.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.util.TypedValue;
import android.view.View;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.components.AxisBase;
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 com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.CombinedData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -29,12 +59,15 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
@ -60,29 +93,36 @@ import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
* shift the date by one day.
*/
public abstract class AbstractChartFragment extends AbstractGBFragment {
protected int ANIM_TIME = 350;
protected final int ANIM_TIME = 250;
private static final Logger LOG = LoggerFactory.getLogger(AbstractChartFragment.class);
private final Set<String> mIntentFilterActions;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
AbstractChartFragment.this.onReceive(context, intent);
}
};
private boolean mChartDirty = true;
private AsyncTask refreshTask;
public boolean isChartDirty() {
return mChartDirty;
}
@Override
public abstract String getTitle();
public boolean supportsHeartrate(GBDevice device) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
return coordinator != null && coordinator.supportsHeartRateMeasurement(device);
}
protected static final class ActivityConfig {
public final int type;
public final String label;
public Integer color;
public final Integer color;
public ActivityConfig(int kind, String label, Integer color) {
this.type = kind;
@ -101,19 +141,23 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected int DESCRIPTION_COLOR;
protected int CHART_TEXT_COLOR;
protected int LEGEND_TEXT_COLOR;
protected int HEARTRATE_COLOR;
protected int HEARTRATE_FILL_COLOR;
protected int AK_ACTIVITY_COLOR;
protected int AK_DEEP_SLEEP_COLOR;
protected int AK_LIGHT_SLEEP_COLOR;
protected int AK_NOT_WORN_COLOR;
protected String HEARTRATE_LABEL;
protected AbstractChartFragment(String... intentFilterActions) {
mIntentFilterActions = new HashSet<>();
if (intentFilterActions != null) {
mIntentFilterActions.addAll(Arrays.asList(intentFilterActions));
mIntentFilterActions.add(ChartsHost.DATE_NEXT);
mIntentFilterActions.add(ChartsHost.DATE_PREV);
mIntentFilterActions.add(ChartsHost.REFRESH);
}
mIntentFilterActions.add(ChartsHost.DATE_NEXT);
mIntentFilterActions.add(ChartsHost.DATE_PREV);
mIntentFilterActions.add(ChartsHost.REFRESH);
}
@Override
@ -130,14 +174,22 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
protected void init() {
BACKGROUND_COLOR = getResources().getColor(R.color.background_material_light);
DESCRIPTION_COLOR = getResources().getColor(R.color.primarytext);
CHART_TEXT_COLOR = getResources().getColor(R.color.secondarytext);
LEGEND_TEXT_COLOR = getResources().getColor(R.color.primarytext);
AK_ACTIVITY_COLOR = getResources().getColor(R.color.chart_activity_light);
AK_DEEP_SLEEP_COLOR = getResources().getColor(R.color.chart_light_sleep_light);
AK_LIGHT_SLEEP_COLOR = getResources().getColor(R.color.chart_deep_sleep_light);
AK_NOT_WORN_COLOR = getResources().getColor(R.color.chart_not_worn_light);
TypedValue runningColor = new TypedValue();
BACKGROUND_COLOR = GBApplication.getBackgroundColor(getContext());
LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(getContext());
CHART_TEXT_COLOR = ContextCompat.getColor(getContext(), R.color.secondarytext);
HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate);
HEARTRATE_FILL_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_fill);
getContext().getTheme().resolveAttribute(R.attr.chart_activity, runningColor, true);
AK_ACTIVITY_COLOR = runningColor.data;
getContext().getTheme().resolveAttribute(R.attr.chart_deep_sleep, runningColor, true);
AK_DEEP_SLEEP_COLOR = runningColor.data;
getContext().getTheme().resolveAttribute(R.attr.chart_light_sleep, runningColor, true);
AK_LIGHT_SLEEP_COLOR = runningColor.data;
getContext().getTheme().resolveAttribute(R.attr.chart_not_worn, runningColor, true);
AK_NOT_WORN_COLOR = runningColor.data;
HEARTRATE_LABEL = getContext().getString(R.string.charts_legend_heartrate);
akActivity = new ActivityConfig(ActivityKind.TYPE_ACTIVITY, getString(R.string.abstract_chart_fragment_kind_activity), AK_ACTIVITY_COLOR);
akLightSleep = new ActivityConfig(ActivityKind.TYPE_LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR);
@ -153,7 +205,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected ChartsHost getChartsHost() {
return (ChartsHost) getActivity();
}
private void setEndDate(Date date) {
getChartsHost().setEndDate(date);
}
@ -182,7 +234,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
protected void showDateBar(boolean show) {
getChartsHost().getDateBar().setVisibility(show ? View.VISIBLE : View.GONE);
getChartsHost().getDateBar().setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
@Override
@ -269,9 +321,9 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
return akActivity.color;
}
protected SampleProvider getProvider(GBDevice device) {
protected SampleProvider<? extends AbstractActivitySample> getProvider(DBHandler db, GBDevice device) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
return coordinator.getSampleProvider();
return coordinator.getSampleProvider(device, db.getDaoSession());
}
/**
@ -282,51 +334,48 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
* @param tsFrom
* @param tsTo
*/
protected List<ActivitySample> getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
SampleProvider provider = getProvider(device);
return db.getAllActivitySamples(tsFrom, tsTo, provider);
protected List<? extends ActivitySample> getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
return provider.getAllActivitySamples(tsFrom, tsTo);
}
private int getTSLast24Hours(int tsTo) {
return (tsTo) - (24 * 60 * 60); // -24 hours
}
protected List<ActivitySample> getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
SampleProvider provider = getProvider(device);
return db.getActivitySamples(tsFrom, tsTo, provider);
protected List<? extends AbstractActivitySample> getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
SampleProvider<? extends AbstractActivitySample> provider = getProvider(db, device);
return provider.getActivitySamples(tsFrom, tsTo);
}
protected List<ActivitySample> getSleepSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
SampleProvider provider = getProvider(device);
return db.getSleepSamples(tsFrom, tsTo, provider);
}
protected List<ActivitySample> getTestSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(2015, Calendar.JUNE, 10, 6, 40);
// ignore provided date ranges
tsTo = (int) ((cal.getTimeInMillis() / 1000));
tsFrom = tsTo - (24 * 60 * 60);
SampleProvider provider = getProvider(device);
return db.getAllActivitySamples(tsFrom, tsTo, provider);
protected List<? extends ActivitySample> getSleepSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
return provider.getSleepSamples(tsFrom, tsTo);
}
protected void configureChartDefaults(Chart<?> chart) {
chart.getXAxis().setValueFormatter(new TimestampValueFormatter());
chart.getDescription().setText("");
// if enabled, the chart will always start at zero on the y-axis
chart.setNoDataText(getString(R.string.chart_no_data_synchronize));
// disable value highlighting
chart.setHighlightEnabled(false);
chart.setHighlightPerTapEnabled(false);
// enable touch gestures
chart.setTouchEnabled(true);
// commented out: this has weird bugs/sideeffects at least on WeekStepsCharts
// where only the first Day-label is drawn, because AxisRenderer.computeAxisValues(float,float)
// appears to have an overflow when calculating 'n' (number of entries)
// chart.getXAxis().setGranularity(60*5);
setupLegend(chart);
}
protected void configureBarLineChartDefaults(BarLineChartBase<?> chart) {
configureChartDefaults(chart);
if (chart instanceof BarChart) {
((BarChart) chart).setFitBars(true);
}
// enable scaling and dragging
chart.setDragEnabled(true);
@ -349,7 +398,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
if (chartsHost.getDevice() != null) {
mChartDirty = false;
updateDateInfo(getStartDate(), getEndDate());
createRefreshTask("Visualizing data", getActivity()).execute();
if (refreshTask != null && refreshTask.getStatus() != AsyncTask.Status.FINISHED) {
refreshTask.cancel(true);
}
refreshTask = createRefreshTask("Visualizing data", getActivity()).execute();
}
}
}
@ -357,9 +409,9 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
/**
* This method reads the data from the database, analyzes and prepares it for
* the charts. This will be called from a background task, so there must not be
* any UI access. #renderCharts will be automatically called after this method.
* any UI access. #updateChartsInUIThread and #renderCharts will be automatically called after this method.
*/
protected abstract void refreshInBackground(DBHandler db, GBDevice device);
protected abstract ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device);
/**
* Triggers the actual (re-) rendering of the chart.
@ -367,44 +419,47 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
*/
protected abstract void renderCharts();
protected void refresh(GBDevice gbDevice, BarLineChartBase chart, List<ActivitySample> samples) {
Calendar cal = GregorianCalendar.getInstance();
cal.clear();
Date date;
String dateStringFrom = "";
String dateStringTo = "";
protected DefaultChartsData<CombinedData> refresh(GBDevice gbDevice, List<? extends ActivitySample> samples) {
// Calendar cal = GregorianCalendar.getInstance();
// cal.clear();
TimestampTranslation tsTranslation = new TimestampTranslation();
// Date date;
// String dateStringFrom = "";
// String dateStringTo = "";
// ArrayList<String> xLabels = null;
LOG.info("" + getTitle() + ": number of samples:" + samples.size());
CombinedData combinedData;
if (samples.size() > 1) {
float movement_divisor;
boolean annotate = true;
boolean use_steps_as_movement;
SampleProvider provider = getProvider(gbDevice);
int last_type = ActivityKind.TYPE_UNKNOWN;
SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
SimpleDateFormat annotationDateFormat = new SimpleDateFormat("HH:mm");
int numEntries = samples.size();
List<String> xLabels = new ArrayList<>(numEntries);
List<BarEntry> activityEntries = new ArrayList<>(numEntries);
boolean hr = supportsHeartrate(gbDevice);
List<Entry> heartrateEntries = hr ? new ArrayList<Entry>(numEntries) : null;
List<Integer> colors = new ArrayList<>(numEntries); // this is kinda inefficient...
int lastHrSampleIndex = -1;
for (int i = 0; i < numEntries; i++) {
ActivitySample sample = samples.get(i);
int type = sample.getKind();
int ts = tsTranslation.shorten(sample.getTimestamp());
// System.out.println(ts);
// ts = i;
// determine start and end dates
if (i == 0) {
cal.setTimeInMillis(sample.getTimestamp() * 1000L); // make sure it's converted to long
date = cal.getTime();
dateStringFrom = dateFormat.format(date);
} else if (i == samples.size() - 1) {
cal.setTimeInMillis(sample.getTimestamp() * 1000L); // same here
date = cal.getTime();
dateStringTo = dateFormat.format(date);
}
// if (i == 0) {
// cal.setTimeInMillis(ts * 1000L); // make sure it's converted to long
// date = cal.getTime();
// dateStringFrom = dateFormat.format(date);
// } else if (i == samples.size() - 1) {
// cal.setTimeInMillis(ts * 1000L); // same here
// date = cal.getTime();
// dateStringTo = dateFormat.format(date);
// }
float movement = sample.getIntensity();
@ -430,14 +485,23 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
// value = ((float) movement) / movement_divisor;
colors.add(akActivity.color);
}
activityEntries.add(createBarEntry(value, i));
activityEntries.add(createBarEntry(value, ts));
if (hr && isValidHeartRateValue(sample.getHeartRate())) {
if (lastHrSampleIndex > -1 && ts - lastHrSampleIndex > 1800*HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
heartrateEntries.add(createLineEntry(0, lastHrSampleIndex + 1));
heartrateEntries.add(createLineEntry(0, ts - 1));
}
heartrateEntries.add(createLineEntry(sample.getHeartRate(), ts));
lastHrSampleIndex = ts;
}
String xLabel = "";
if (annotate) {
cal.setTimeInMillis(sample.getTimestamp() * 1000L);
date = cal.getTime();
String dateString = annotationDateFormat.format(date);
xLabel = dateString;
// cal.setTimeInMillis((ts + tsOffset) * 1000L);
// date = cal.getTime();
// String dateString = annotationDateFormat.format(date);
// xLabel = dateString;
// if (last_type != type) {
// if (isSleep(last_type) && !isSleep(type)) {
// // woken up
@ -455,30 +519,39 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
// chart.getXAxis().addLimitLine(line);
// }
// }
last_type = type;
// last_type = type;
}
xLabels.add(xLabel);
}
chart.getXAxis().setValues(xLabels);
BarDataSet activitySet = createActivitySet(activityEntries, colors, "Activity");
ArrayList<BarDataSet> dataSets = new ArrayList<>();
dataSets.add(activitySet);
// create a data object with the datasets
BarData data = new BarData(xLabels, dataSets);
data.setGroupSpace(0);
// combinedData = new CombinedData(xLabels);
combinedData = new CombinedData();
List<IBarDataSet> list = new ArrayList<>();
list.add(activitySet);
BarData barData = new BarData(list);
barData.setBarWidth(200f);
// barData.setGroupSpace(0);
combinedData.setData(barData);
if (hr && heartrateEntries.size() > 0) {
LineDataSet heartrateSet = createHeartrateSet(heartrateEntries, "Heart Rate");
LineData lineData = new LineData(heartrateSet);
combinedData.setData(lineData);
}
chart.setDescription("");
// chart.setDescription(getString(R.string.sleep_activity_date_range, dateStringFrom, dateStringTo));
// chart.setDescriptionPosition(?, ?);
setupLegend(chart);
chart.setData(data);
} else {
combinedData = new CombinedData();
}
IAxisValueFormatter xValueFormatter = new SampleXLabelFormatter(tsTranslation);
return new DefaultChartsData(combinedData, xValueFormatter);
}
protected boolean isValidHeartRateValue(int value) {
return value > HeartRateUtils.MIN_HEART_RATE_VALUE && value < HeartRateUtils.MAX_HEART_RATE_VALUE;
}
/**
@ -490,12 +563,16 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
* @param tsTo
* @return
*/
protected abstract List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo);
protected abstract List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo);
protected abstract void setupLegend(Chart chart);
protected BarEntry createBarEntry(float value, int index) {
return new BarEntry(value, index);
protected BarEntry createBarEntry(float value, int xValue) {
return new BarEntry(xValue, value);
}
protected Entry createLineEntry(float value, int xValue) {
return new Entry(xValue, value);
}
protected BarDataSet createActivitySet(List<BarEntry> values, List<Integer> colors, String label) {
@ -512,6 +589,28 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
// set1.setHighLightColor(Color.rgb(128, 0, 255));
// set1.setColor(Color.rgb(89, 178, 44));
set1.setValueTextColor(CHART_TEXT_COLOR);
set1.setAxisDependency(YAxis.AxisDependency.LEFT);
return set1;
}
protected LineDataSet createHeartrateSet(List<Entry> values, String label) {
LineDataSet set1 = new LineDataSet(values, label);
set1.setLineWidth(2.2f);
set1.setColor(HEARTRATE_COLOR);
// set1.setDrawCubic(true);
set1.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
set1.setCubicIntensity(0.1f);
set1.setDrawCircles(false);
// set1.setCircleRadius(2f);
// set1.setDrawFilled(true);
// set1.setColor(getResources().getColor(android.R.color.background_light));
// set1.setCircleColor(HEARTRATE_COLOR);
// set1.setFillColor(ColorTemplate.getHoloBlue());
// set1.setHighLightColor(Color.rgb(128, 0, 255));
// set1.setColor(Color.rgb(89, 178, 44));
set1.setDrawValues(true);
set1.setValueTextColor(CHART_TEXT_COLOR);
set1.setAxisDependency(YAxis.AxisDependency.RIGHT);
return set1;
}
@ -554,6 +653,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
public class RefreshTask extends DBAccess {
private ChartsData chartsData;
public RefreshTask(String task, Context context) {
super(task, context);
}
@ -562,15 +663,18 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected void doInBackground(DBHandler db) {
ChartsHost chartsHost = getChartsHost();
if (chartsHost != null) {
refreshInBackground(db, chartsHost.getDevice());
chartsData = refreshInBackground(chartsHost, db, chartsHost.getDevice());
} else {
cancel(true);
}
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
FragmentActivity activity = (FragmentActivity) getActivity();
FragmentActivity activity = getActivity();
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
updateChartsnUIThread(chartsData);
renderCharts();
} else {
LOG.info("Not rendering charts because activity is not available anymore");
@ -578,6 +682,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
}
protected abstract void updateChartsnUIThread(ChartsData chartsData);
/**
* Returns true if the date was successfully shifted, and false if the shift
* was ignored, e.g. when the to-value is in the future.
@ -606,8 +712,46 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
}
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device) {
return getSamples(db, device, getTSStart(), getTSEnd());
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device) {
int tsStart = getTSStart();
int tsEnd = getTSEnd();
List<ActivitySample> samples = (List<ActivitySample>) getSamples(db, device, tsStart, tsEnd);
ensureStartAndEndSamples(samples, tsStart, tsEnd);
// List<ActivitySample> samples2 = new ArrayList<>();
// int min = Math.min(samples.size(), 10);
// int min = Math.min(samples.size(), 10);
// for (int i = 0; i < min; i++) {
// samples2.add(samples.get(i));
// }
// return samples2;
return samples;
}
protected void ensureStartAndEndSamples(List<ActivitySample> samples, int tsStart, int tsEnd) {
if (samples == null || samples.isEmpty()) {
return;
}
ActivitySample lastSample = samples.get(samples.size() - 1);
if (lastSample.getTimestamp() < tsEnd) {
samples.add(createTrailingActivitySample(lastSample, tsEnd));
}
ActivitySample firstSample = samples.get(0);
if (firstSample.getTimestamp() > tsStart) {
samples.add(createTrailingActivitySample(firstSample, tsStart));
}
}
private ActivitySample createTrailingActivitySample(ActivitySample referenceSample, int timestamp) {
TrailingActivitySample sample = new TrailingActivitySample();
if (referenceSample instanceof AbstractActivitySample) {
AbstractActivitySample reference = (AbstractActivitySample) referenceSample;
sample.setUserId(reference.getUserId());
sample.setDeviceId(reference.getDeviceId());
sample.setProvider(reference.getProvider());
}
sample.setTimestamp(timestamp);
return sample;
}
private int getTSEnd() {
@ -621,4 +765,88 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
private int toTimestamp(Date date) {
return (int) ((date.getTime() / 1000));
}
public static class DefaultChartsData<T extends ChartData<?>> extends ChartsData {
private final T data;
private IAxisValueFormatter xValueFormatter;
public DefaultChartsData(T data, IAxisValueFormatter xValueFormatter) {
this.xValueFormatter = xValueFormatter;
this.data = data;
}
public IAxisValueFormatter getXValueFormatter() {
return xValueFormatter;
}
public T getData() {
return data;
}
}
protected static class SampleXLabelFormatter implements IAxisValueFormatter {
private final TimestampTranslation tsTranslation;
SimpleDateFormat annotationDateFormat = new SimpleDateFormat("HH:mm");
// SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
Calendar cal = GregorianCalendar.getInstance();
public SampleXLabelFormatter(TimestampTranslation tsTranslation) {
this.tsTranslation = tsTranslation;
}
// TODO: this does not work. Cannot use precomputed labels
@Override
public String getFormattedValue(float value, AxisBase axis) {
cal.clear();
int ts = (int) value;
cal.setTimeInMillis(tsTranslation.toOriginalValue(ts) * 1000L);
Date date = cal.getTime();
String dateString = annotationDateFormat.format(date);
return dateString;
}
}
protected static class PreformattedXIndexLabelFormatter implements IAxisValueFormatter {
private ArrayList<String> xLabels;
public PreformattedXIndexLabelFormatter(ArrayList<String> xLabels) {
this.xLabels = xLabels;
}
@Override
public String getFormattedValue(float value, AxisBase axis) {
int index = (int) value;
if (xLabels == null || index >= xLabels.size()) {
return String.valueOf(value);
}
return xLabels.get(index);
}
}
/**
* Awkward class that helps in translating long timestamp
* values to float (sic!) values. It basically rebases all
* timestamps to a base (the very first) timestamp value.
*
* It does this so that the large timestamp values can be used
* floating point values, where the mantissa is just 24 bits.
*/
protected static class TimestampTranslation {
private int tsOffset = -1;
public int shorten(int timestamp) {
if (tsOffset == -1) {
tsOffset = timestamp;
return 0;
}
return timestamp - tsOffset;
}
public int toOriginalValue(int timestamp) {
if (tsOffset == -1) {
return timestamp;
}
return timestamp + tsOffset;
}
}
}

View File

@ -0,0 +1,327 @@
/* Copyright (C) 2015-2017 0nse, Alberto, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.components.LimitLine;
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 com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.data.PieEntry;
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
import com.github.mikephil.charting.formatter.IValueFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class);
private Locale mLocale;
private int mTargetValue = 0;
private PieChart mTodayPieChart;
private BarChart mWeekChart;
private int mOffsetHours = getOffsetHours();
@Override
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
Calendar day = Calendar.getInstance();
day.setTime(chartsHost.getEndDate());
//NB: we could have omitted the day, but this way we can move things to the past easily
DayData dayData = refreshDayPie(db, day, device);
DefaultChartsData weekBeforeData = refreshWeekBeforeData(db, mWeekChart, day, device);
return new MyChartsData(dayData, weekBeforeData);
}
@Override
protected void updateChartsnUIThread(ChartsData chartsData) {
MyChartsData mcd = (MyChartsData) chartsData;
setupLegend(mWeekChart);
mTodayPieChart.setCenterText(mcd.getDayData().centerText);
mTodayPieChart.setData(mcd.getDayData().data);
mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
mWeekChart.setData(mcd.getWeekBeforeData().getData());
mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter());
}
@Override
protected void renderCharts() {
mWeekChart.invalidate();
mTodayPieChart.invalidate();
}
private DefaultChartsData<BarData> refreshWeekBeforeData(DBHandler db, BarChart barChart, Calendar day, GBDevice device) {
day = (Calendar) day.clone(); // do not modify the caller's argument
day.add(Calendar.DATE, -7);
List<BarEntry> entries = new ArrayList<>();
ArrayList<String> labels = new ArrayList<String>();
for (int counter = 0; counter < 7; counter++) {
ActivityAmounts amounts = getActivityAmountsForDay(db, day, device);
entries.add(new BarEntry(counter, getTotalsForActivityAmounts(amounts)));
labels.add(day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale));
day.add(Calendar.DATE, 1);
}
BarDataSet set = new BarDataSet(entries, "");
set.setColors(getColors());
set.setValueFormatter(getBarValueFormatter());
BarData barData = new BarData(set);
barData.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false);
barData.setValueTextSize(10f);
LimitLine target = new LimitLine(mTargetValue);
barChart.getAxisLeft().removeAllLimitLines();
barChart.getAxisLeft().addLimitLine(target);
return new DefaultChartsData(barData, new PreformattedXIndexLabelFormatter(labels));
}
private DayData refreshDayPie(DBHandler db, Calendar day, GBDevice device) {
PieData data = new PieData();
List<PieEntry> entries = new ArrayList<>();
PieDataSet set = new PieDataSet(entries, "");
ActivityAmounts amounts = getActivityAmountsForDay(db, day, device);
float totalValues[] = getTotalsForActivityAmounts(amounts);
String[] pieLabels = getPieLabels();
float totalValue = 0;
for (int i = 0; i < totalValues.length; i++) {
float value = totalValues[i];
totalValue += value;
entries.add(new PieEntry(value, pieLabels[i]));
}
set.setColors(getColors());
if (totalValues.length < 2) {
if (totalValue < mTargetValue) {
entries.add(new PieEntry((mTargetValue - totalValue)));
set.addColor(Color.GRAY);
}
}
data.setDataSet(set);
if (totalValues.length < 2) {
data.setDrawValues(false);
}
else {
set.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
set.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
set.setValueTextColor(DESCRIPTION_COLOR);
set.setValueTextSize(13f);
set.setValueFormatter(getPieValueFormatter());
}
return new DayData(data, formatPieValue((int) totalValue));
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mLocale = getResources().getConfiguration().locale;
View rootView = inflater.inflate(R.layout.fragment_weeksteps_chart, container, false);
int goal = getGoal();
if (goal >= 0) {
mTargetValue = goal;
}
mTodayPieChart = (PieChart) rootView.findViewById(R.id.todaystepschart);
mWeekChart = (BarChart) rootView.findViewById(R.id.weekstepschart);
setupWeekChart();
setupTodayPieChart();
// refresh immediately instead of use refreshIfVisible(), for perceived performance
refresh();
return rootView;
}
private void setupTodayPieChart() {
mTodayPieChart.setBackgroundColor(BACKGROUND_COLOR);
mTodayPieChart.getDescription().setTextColor(DESCRIPTION_COLOR);
mTodayPieChart.setEntryLabelColor(DESCRIPTION_COLOR);
mTodayPieChart.getDescription().setText(getPieDescription(mTargetValue));
// mTodayPieChart.setNoDataTextDescription("");
mTodayPieChart.setNoDataText("");
mTodayPieChart.getLegend().setEnabled(false);
}
private void setupWeekChart() {
mWeekChart.setBackgroundColor(BACKGROUND_COLOR);
mWeekChart.getDescription().setTextColor(DESCRIPTION_COLOR);
mWeekChart.getDescription().setText("");
mWeekChart.setFitBars(true);
configureBarLineChartDefaults(mWeekChart);
XAxis x = mWeekChart.getXAxis();
x.setDrawLabels(true);
x.setDrawGridLines(false);
x.setEnabled(true);
x.setTextColor(CHART_TEXT_COLOR);
x.setDrawLimitLinesBehindData(true);
x.setPosition(XAxis.XAxisPosition.BOTTOM);
YAxis y = mWeekChart.getAxisLeft();
y.setDrawGridLines(false);
y.setDrawTopYLabelEntry(false);
y.setTextColor(CHART_TEXT_COLOR);
y.setDrawZeroLine(true);
y.setSpaceBottom(0);
y.setAxisMinimum(0);
y.setValueFormatter(getYAxisFormatter());
y.setEnabled(true);
YAxis yAxisRight = mWeekChart.getAxisRight();
yAxisRight.setDrawGridLines(false);
yAxisRight.setEnabled(false);
yAxisRight.setDrawLabels(false);
yAxisRight.setDrawTopYLabelEntry(false);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
}
private List<? extends ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, int offsetHours, GBDevice device) {
int startTs;
int endTs;
day = (Calendar) day.clone(); // do not modify the caller's argument
day.set(Calendar.HOUR_OF_DAY, 0);
day.set(Calendar.MINUTE, 0);
day.set(Calendar.SECOND, 0);
day.add(Calendar.HOUR, offsetHours);
startTs = (int) (day.getTimeInMillis() / 1000);
endTs = startTs + 24 * 60 * 60 - 1;
return getSamples(db, device, startTs, endTs);
}
@Override
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
return super.getAllSamples(db, device, tsFrom, tsTo);
}
private static class DayData {
private final PieData data;
private final CharSequence centerText;
DayData(PieData data, String centerText) {
this.data = data;
this.centerText = centerText;
}
}
private static class MyChartsData extends ChartsData {
private final DefaultChartsData<BarData> weekBeforeData;
private final DayData dayData;
MyChartsData(DayData dayData, DefaultChartsData<BarData> weekBeforeData) {
this.dayData = dayData;
this.weekBeforeData = weekBeforeData;
}
DayData getDayData() {
return dayData;
}
DefaultChartsData<BarData> getWeekBeforeData() {
return weekBeforeData;
}
}
private ActivityAmounts getActivityAmountsForDay(DBHandler db, Calendar day, GBDevice device) {
LimitedQueue activityAmountCache = null;
ActivityAmounts amounts = null;
Activity activity = getActivity();
int key = (int) (day.getTimeInMillis() / 1000) + (mOffsetHours * 3600);
if (activity != null) {
activityAmountCache = ((ChartsActivity) activity).mActivityAmountCache;
amounts = (ActivityAmounts) (activityAmountCache.lookup(key));
}
if (amounts == null) {
ActivityAnalysis analysis = new ActivityAnalysis();
amounts = analysis.calculateActivityAmounts(getSamplesOfDay(db, day, mOffsetHours, device));
if (activityAmountCache != null) {
activityAmountCache.add(key, amounts);
}
}
return amounts;
}
abstract int getGoal();
abstract int getOffsetHours();
abstract float[] getTotalsForActivityAmounts(ActivityAmounts activityAmounts);
abstract String formatPieValue(int value);
abstract String[] getPieLabels();
abstract IValueFormatter getPieValueFormatter();
abstract IValueFormatter getBarValueFormatter();
abstract IAxisValueFormatter getYAxisFormatter();
abstract int[] getColors();
abstract String getPieDescription(int targetValue);
}

View File

@ -1,14 +1,43 @@
/* Copyright (C) 2015-2017 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 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;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
public class ActivityAnalysis {
public ActivityAmounts calculateActivityAmounts(List<ActivitySample> samples) {
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);
ActivityAmount notWorn = new ActivityAmount(ActivityKind.TYPE_NOT_WORN);
@ -17,7 +46,7 @@ public class ActivityAnalysis {
ActivityAmount previousAmount = null;
ActivitySample previousSample = null;
for (ActivitySample sample : samples) {
ActivityAmount amount = null;
ActivityAmount amount;
switch (sample.getKind()) {
case ActivityKind.TYPE_DEEP_SLEEP:
amount = deepSleep;
@ -34,6 +63,11 @@ public class ActivityAnalysis {
break;
}
int steps = sample.getSteps();
if (steps > 0) {
amount.addSteps(steps);
}
if (previousSample != null) {
long timeDifference = sample.getTimestamp() - previousSample.getTimestamp();
if (previousSample.getRawKind() == sample.getRawKind()) {
@ -43,8 +77,22 @@ public class ActivityAnalysis {
previousAmount.addSeconds(sharedTimeDifference);
amount.addSeconds(sharedTimeDifference);
}
} else {
// nothing to do, we can only calculate when we have the next sample
// 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;
@ -66,10 +114,13 @@ public class ActivityAnalysis {
return result;
}
public int calculateTotalSteps(List<ActivitySample> samples) {
int calculateTotalSteps(List<? extends ActivitySample> samples) {
int totalSteps = 0;
for (ActivitySample sample : samples) {
totalSteps += sample.getSteps();
int steps = sample.getSteps();
if (steps > 0) {
totalSteps += sample.getSteps();
}
}
return totalSteps;
}

View File

@ -1,3 +1,20 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.content.Context;
@ -10,6 +27,7 @@ import android.view.ViewGroup;
import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.components.LegendEntry;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
@ -20,6 +38,7 @@ import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
@ -54,7 +73,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
private void setupChart() {
mChart.setBackgroundColor(BACKGROUND_COLOR);
mChart.setDescriptionColor(DESCRIPTION_COLOR);
mChart.getDescription().setTextColor(DESCRIPTION_COLOR);
configureBarLineChartDefaults(mChart);
@ -69,7 +88,8 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
y.setDrawGridLines(false);
// y.setDrawLabels(false);
// TODO: make fixed max value optional
y.setAxisMaxValue(1f);
y.setAxisMaximum(1f);
y.setAxisMinimum(0);
y.setDrawTopYLabelEntry(false);
y.setTextColor(CHART_TEXT_COLOR);
@ -78,10 +98,12 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
YAxis yAxisRight = mChart.getAxisRight();
yAxisRight.setDrawGridLines(false);
yAxisRight.setEnabled(false);
yAxisRight.setDrawLabels(false);
yAxisRight.setDrawTopYLabelEntry(false);
yAxisRight.setEnabled(supportsHeartrate(getChartsHost().getDevice()));
yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaximum(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinimum(HeartRateUtils.MIN_HEART_RATE_VALUE);
// refresh immediately instead of use refreshIfVisible(), for perceived performance
refresh();
@ -103,33 +125,61 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
}
@Override
protected void refreshInBackground(DBHandler db, GBDevice device) {
List<ActivitySample> samples = getSamples(db, device);
refresh(device, mChart, samples);
mChart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
}
protected void renderCharts() {
mChart.animateX(ANIM_TIME, Easing.EasingOption.EaseInOutQuart);
}
protected void setupLegend(Chart chart) {
List<Integer> legendColors = new ArrayList<>(4);
List<String> legendLabels = new ArrayList<>(4);
legendColors.add(akActivity.color);
legendLabels.add(akActivity.label);
legendColors.add(akLightSleep.color);
legendLabels.add(akLightSleep.label);
legendColors.add(akDeepSleep.color);
legendLabels.add(akDeepSleep.label);
legendColors.add(akNotWorn.color);
legendLabels.add(akNotWorn.label);
chart.getLegend().setCustom(legendColors, legendLabels);
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
List<? extends ActivitySample> samples = getSamples(db, device);
return refresh(device, samples);
}
@Override
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
protected void updateChartsnUIThread(ChartsData chartsData) {
DefaultChartsData dcd = (DefaultChartsData) chartsData;
mChart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
mChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
mChart.getXAxis().setValueFormatter(dcd.getXValueFormatter());
mChart.setData(dcd.getData());
}
@Override
protected void renderCharts() {
mChart.animateX(ANIM_TIME, Easing.EasingOption.EaseInOutQuart);
// mChart.invalidate();
}
@Override
protected void setupLegend(Chart chart) {
List<LegendEntry> legendEntries = new ArrayList<>(5);
LegendEntry activityEntry = new LegendEntry();
activityEntry.label = akActivity.label;
activityEntry.formColor = akActivity.color;
legendEntries.add(activityEntry);
LegendEntry lightSleepEntry = new LegendEntry();
lightSleepEntry.label = akLightSleep.label;
lightSleepEntry.formColor = akLightSleep.color;
legendEntries.add(lightSleepEntry);
LegendEntry deepSleepEntry = new LegendEntry();
deepSleepEntry.label = akDeepSleep.label;
deepSleepEntry.formColor = akDeepSleep.color;
legendEntries.add(deepSleepEntry);
LegendEntry notWornEntry = new LegendEntry();
notWornEntry.label = akNotWorn.label;
notWornEntry.formColor = akNotWorn.color;
legendEntries.add(notWornEntry);
if (supportsHeartrate(getChartsHost().getDevice())) {
LegendEntry hrEntry = new LegendEntry();
hrEntry.label = HEARTRATE_LABEL;
hrEntry.formColor = HEARTRATE_COLOR;
legendEntries.add(hrEntry);
}
chart.getLegend().setCustom(legendEntries);
}
@Override
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
return getAllSamples(db, device, tsFrom, tsTo);
}
}

View File

@ -1,3 +1,20 @@
/* Copyright (C) 2015-2017 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.app.Dialog;
@ -11,13 +28,15 @@ import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -29,27 +48,33 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractFragmentPagerAdapter;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragmentActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
public class ChartsActivity extends AbstractGBFragmentActivity implements ChartsHost {
private static final Logger LOG = LoggerFactory.getLogger(ChartsActivity.class);
private ProgressBar mProgressBar;
private Button mPrevButton;
private Button mNextButton;
private TextView mDateControl;
private Date mStartDate;
private Date mEndDate;
private SwipeRefreshLayout swipeLayout;
private ViewPager viewPager;
LimitedQueue mActivityAmountCache = new LimitedQueue(60);
private static class ShowDurationDialog extends Dialog {
private final String mDuration;
private TextView durationLabel;
public ShowDurationDialog(String duration, Context context) {
ShowDurationDialog(String duration, Context context) {
super(context);
mDuration = duration;
}
@ -72,14 +97,11 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
}
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case ControlCenter.ACTION_QUIT:
finish();
break;
case GBDevice.ACTION_DEVICE_CHANGED:
GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
refreshBusyState(dev);
@ -92,14 +114,15 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
private void refreshBusyState(GBDevice dev) {
if (dev.isBusy()) {
mProgressBar.setVisibility(View.VISIBLE);
swipeLayout.setRefreshing(true);
} else {
boolean wasBusy = mProgressBar.getVisibility() != View.GONE;
boolean wasBusy = swipeLayout.isRefreshing();
swipeLayout.setRefreshing(false);
if (wasBusy) {
mProgressBar.setVisibility(View.GONE);
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(REFRESH));
}
}
enableSwipeRefresh(true);
}
@Override
@ -110,7 +133,6 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
initDates();
IntentFilter filterLocal = new IntentFilter();
filterLocal.addAction(ControlCenter.ACTION_QUIT);
filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
@ -121,9 +143,32 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
throw new IllegalArgumentException("Must provide a device when invoking this activity");
}
swipeLayout = (SwipeRefreshLayout) findViewById(R.id.activity_swipe_layout);
swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
fetchActivityData();
}
});
enableSwipeRefresh(true);
// Set up the ViewPager with the sections adapter.
ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
viewPager = (ViewPager) findViewById(R.id.charts_pager);
viewPager.setAdapter(getPagerAdapter());
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
enableSwipeRefresh(state == ViewPager.SCROLL_STATE_IDLE);
}
});
dateBar = (ViewGroup) findViewById(R.id.charts_date_bar);
mDateControl = (TextView) findViewById(R.id.charts_text_date);
@ -135,7 +180,6 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
}
});
mProgressBar = (ProgressBar) findViewById(R.id.charts_progress);
mPrevButton = (Button) findViewById(R.id.charts_previous);
mPrevButton.setOnClickListener(new View.OnClickListener() {
@Override
@ -150,6 +194,8 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
handleNextButtonClicked();
}
});
LinearLayout mainLayout = (LinearLayout) findViewById(R.id.charts_main_layout);
}
private String formatDetailedDuration() {
@ -208,6 +254,11 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.menu_charts, menu);
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice);
if (!mGBDevice.isConnected() || !coordinator.supportsActivityDataFetching()) {
menu.removeItem(R.id.charts_fetch_activity_data);
}
return true;
}
@ -215,7 +266,7 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.charts_fetch_activity_data:
GBApplication.deviceService().onFetchActivityData();
fetchActivityData();
return true;
default:
break;
@ -224,6 +275,20 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
return super.onOptionsItemSelected(item);
}
private void enableSwipeRefresh(boolean enable) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice);
swipeLayout.setEnabled(enable && coordinator.allowFetchActivityData(mGBDevice));
}
private void fetchActivityData() {
if (getDevice().isInitialized()) {
GBApplication.deviceService().onFetchActivityData();
} else {
swipeLayout.setRefreshing(false);
GB.toast(this, getString(R.string.device_not_connected), Toast.LENGTH_SHORT, GB.ERROR);
}
}
@Override
public void setDateInfo(String dateInfo) {
mDateControl.setText(dateInfo);
@ -239,13 +304,14 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
return dateBar;
}
/**
* A {@link FragmentStatePagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public static class SectionsPagerAdapter extends AbstractFragmentPagerAdapter {
public class SectionsPagerAdapter extends AbstractFragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
@ -258,18 +324,44 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
case 1:
return new SleepChartFragment();
case 2:
return new WeekStepsChartFragment();
return new WeekSleepChartFragment();
case 3:
return new WeekStepsChartFragment();
case 4:
return new SpeedZonesFragment();
case 5:
return new LiveActivityFragment();
}
return null;
}
@Override
public int getCount() {
// Show 3 total pages.
return 4;
// Show 5 or 6 total pages.
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice);
if (coordinator.supportsRealtimeData()) {
return 6;
}
return 5;
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.activity_sleepchart_activity_and_sleep);
case 1:
return getString(R.string.sleepchart_your_sleep);
case 2:
return getString(R.string.weeksleepchart_sleep_a_week);
case 3:
return getString(R.string.weekstepschart_steps_a_week);
case 4:
return getString(R.string.stats_title);
case 5:
return getString(R.string.liveactivity_live_activity);
}
return super.getPageTitle(position);
}
}
}

View File

@ -0,0 +1,20 @@
/* Copyright (C) 2016-2017 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.activities.charts;
public abstract class ChartsData {
}

View File

@ -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/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.view.ViewGroup;
@ -7,9 +23,9 @@ import java.util.Date;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public interface ChartsHost {
static final String DATE_PREV = ChartsActivity.class.getName().concat(".date_prev");
static final String DATE_NEXT = ChartsActivity.class.getName().concat(".date_next");
static final String REFRESH = ChartsActivity.class.getName().concat(".refresh");
String DATE_PREV = ChartsActivity.class.getName().concat(".date_prev");
String DATE_NEXT = ChartsActivity.class.getName().concat(".date_next");
String REFRESH = ChartsActivity.class.getName().concat(".refresh");
GBDevice getDevice();

View File

@ -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/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.animation.ValueAnimator;
@ -49,6 +65,7 @@ public class CustomBarChart extends BarChart {
/**
* Call this to set the next value for the Entry to be animated.
* Call animateY() when ready to do that.
*
* @param nextValue
*/
public void setSingleEntryYValue(float nextValue) {

View File

@ -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/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.content.BroadcastReceiver;
@ -7,6 +23,7 @@ import android.content.IntentFilter;
import android.graphics.Paint;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.LocalBroadcastManager;
import android.view.LayoutInflater;
import android.view.View;
@ -16,11 +33,13 @@ import android.widget.Toast;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.components.LimitLine;
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 com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
@ -31,139 +50,217 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.Measurement;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class LiveActivityFragment extends AbstractChartFragment {
private static final Logger LOG = LoggerFactory.getLogger(LiveActivityFragment.class);
private static final int MAX_STEPS_PER_MINUTE = 300;
private static final int MIN_STEPS_PER_MINUTE = 60;
private static final int RESET_COUNT = 10; // reset the max steps per minute value every 10s
private BarEntry totalStepsEntry;
private BarEntry stepsPerMinuteEntry;
private BarDataSet mStepsPerMinuteData;
private BarDataSet mTotalStepsData;
private LineDataSet mHistorySet;
private BarLineChartBase mStepsPerMinuteHistoryChart;
private CustomBarChart mStepsPerMinuteCurrentChart;
private CustomBarChart mTotalStepsChart;
private final Steps mSteps = new Steps();
private ScheduledExecutorService pulseScheduler;
private int maxStepsResetCounter;
private List<Measurement> heartRateValues;
private LineDataSet mHeartRateSet;
private int mHeartRate;
private TimestampTranslation tsTranslation;
private class Steps {
private int initialSteps;
private int steps;
private long lastTimestamp;
private int lastTimestamp;
private int currentStepsPerMinute;
private int maxStepsPerMinute;
private int lastStepsPerMinute;
public int getStepsPerMinute() {
return currentStepsPerMinute;
public int getStepsPerMinute(boolean reset) {
lastStepsPerMinute = currentStepsPerMinute;
int result = currentStepsPerMinute;
if (reset) {
currentStepsPerMinute = 0;
}
return result;
}
public int getTotalSteps() {
return steps - initialSteps;
return steps;
}
public int getMaxStepsPerMinute() {
return maxStepsPerMinute;
}
public void updateCurrentSteps(int newSteps, long timestamp) {
public void updateCurrentSteps(int stepsDelta, int timestamp) {
try {
if (steps == 0) {
steps = newSteps;
steps += stepsDelta;
lastTimestamp = timestamp;
if (newSteps > 0) {
initialSteps = newSteps;
}
return;
}
if (newSteps >= steps) {
int stepsDelta = newSteps - steps;
long timeDelta = timestamp - lastTimestamp;
currentStepsPerMinute = calculateStepsPerMinute(stepsDelta, timeDelta);
maxStepsPerMinute = Math.max(maxStepsPerMinute, currentStepsPerMinute);
steps = newSteps;
lastTimestamp = timestamp;
} else {
// TODO: handle new day?
int timeDelta = timestamp - lastTimestamp;
currentStepsPerMinute = calculateStepsPerMinute(stepsDelta, timeDelta);
if (currentStepsPerMinute > maxStepsPerMinute) {
maxStepsPerMinute = currentStepsPerMinute;
maxStepsResetCounter = 0;
}
steps += stepsDelta;
lastTimestamp = timestamp;
} catch (Exception ex) {
GB.toast(LiveActivityFragment.this.getContext(), ex.getMessage(), Toast.LENGTH_SHORT, GB.ERROR, ex);
}
}
private int calculateStepsPerMinute(int stepsDelta, long millis) {
private int calculateStepsPerMinute(int stepsDelta, int seconds) {
if (stepsDelta == 0) {
return 0; // not walking or not enough data per mills?
}
if (millis <= 0) {
throw new IllegalArgumentException("delta in millis is <= 0 -- time change?");
if (seconds <= 0) {
throw new IllegalArgumentException("delta in seconds is <= 0 -- time change?");
}
long oneMinute = 60 * 1000;
float factor = oneMinute / millis;
return (int) (stepsDelta * factor);
int oneMinute = 60;
float factor = oneMinute / seconds;
int result = (int) (stepsDelta * factor);
if (result > MAX_STEPS_PER_MINUTE) {
// ignore, return previous value instead
result = lastStepsPerMinute;
}
return result;
}
}
private BarLineChartBase mStepsPerMinuteHistoryChart;
private CustomBarChart mStepsPerMinuteCurrentChart;
private CustomBarChart mTotalStepsChart;
private Steps mSteps = new Steps();
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case DeviceService.ACTION_REALTIME_STEPS:
int steps = intent.getIntExtra(DeviceService.EXTRA_REALTIME_STEPS, 0);
long timestamp = intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
refreshCurrentSteps(steps, timestamp);
case DeviceService.ACTION_REALTIME_SAMPLES: {
ActivitySample sample = (ActivitySample) intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE);
addSample(sample);
break;
}
}
}
};
private void refreshCurrentSteps(int steps, long timestamp) {
mSteps.updateCurrentSteps(steps, timestamp);
// Or: count down the steps until goal reached? And then flash GOAL REACHED -> Set stretch goal
mTotalStepsChart.setSingleEntryYValue(mSteps.getTotalSteps());
LOG.info("Steps: " + steps + ", total: " + mSteps.getTotalSteps() + ", current: " + mSteps.getStepsPerMinute());
mStepsPerMinuteCurrentChart.getAxisLeft().setAxisMaxValue(mSteps.getMaxStepsPerMinute());
mStepsPerMinuteCurrentChart.setSingleEntryYValue(mSteps.getStepsPerMinute());
private void addSample(ActivitySample sample) {
int heartRate = sample.getHeartRate();
int timestamp = tsTranslation.shorten(sample.getTimestamp());
if (isValidHeartRateValue(heartRate)) {
setCurrentHeartRate(heartRate, timestamp);
}
int steps = sample.getSteps();
if (steps != ActivitySample.NOT_MEASURED) {
addEntries(steps, timestamp);
}
}
if (mStepsPerMinuteHistoryChart.getData() == null) {
if (mSteps.getTotalSteps() == 0) {
return; // ignore the first default value to keep the "no-data-description" visible
}
LineData data = new LineData();
mStepsPerMinuteHistoryChart.setData(data);
data.addDataSet(mHistorySet);
private int translateTimestampFrom(Intent intent) {
return translateTimestamp(intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis()));
}
private int translateTimestamp(long tsMillis) {
int timestamp = (int) (tsMillis / 1000); // translate to seconds
return tsTranslation.shorten(timestamp); // and shorten
}
private void setCurrentHeartRate(int heartRate, int timestamp) {
addHistoryDataSet(true);
mHeartRate = heartRate;
}
private int getCurrentHeartRate() {
int result = mHeartRate;
mHeartRate = -1;
return result;
}
private void addEntries(int steps, int timestamp) {
mSteps.updateCurrentSteps(steps, timestamp);
if (++maxStepsResetCounter > RESET_COUNT) {
maxStepsResetCounter = 0;
mSteps.maxStepsPerMinute = 0;
}
// Or: count down the steps until goal reached? And then flash GOAL REACHED -> Set stretch goal
LOG.info("Steps: " + steps + ", total: " + mSteps.getTotalSteps() + ", current: " + mSteps.getStepsPerMinute(false));
// addEntries();
}
private void addEntries(int timestamp) {
mTotalStepsChart.setSingleEntryYValue(mSteps.getTotalSteps());
YAxis stepsPerMinuteCurrentYAxis = mStepsPerMinuteCurrentChart.getAxisLeft();
int maxStepsPerMinute = mSteps.getMaxStepsPerMinute();
// int extraRoom = maxStepsPerMinute/5;
// buggy in MPAndroidChart? Disable.
// stepsPerMinuteCurrentYAxis.setAxisMaxValue(Math.max(MIN_STEPS_PER_MINUTE, maxStepsPerMinute + extraRoom));
LimitLine target = new LimitLine(maxStepsPerMinute);
stepsPerMinuteCurrentYAxis.removeAllLimitLines();
stepsPerMinuteCurrentYAxis.addLimitLine(target);
int stepsPerMinute = mSteps.getStepsPerMinute(true);
mStepsPerMinuteCurrentChart.setSingleEntryYValue(stepsPerMinute);
if (!addHistoryDataSet(false)) {
return;
}
LineData historyData = (LineData) mStepsPerMinuteHistoryChart.getData();
historyData.addXValue("");
historyData.addEntry(new Entry(mSteps.getStepsPerMinute(), mHistorySet.getEntryCount()), 0);
ChartData data = mStepsPerMinuteHistoryChart.getData();
if (stepsPerMinute < 0) {
stepsPerMinute = 0;
}
mHistorySet.addEntry(new Entry(timestamp, stepsPerMinute));
int hr = getCurrentHeartRate();
if (hr < 0) {
hr = 0;
}
mHeartRateSet.addEntry(new Entry(timestamp, hr));
}
mTotalStepsData.notifyDataSetChanged();
mStepsPerMinuteData.notifyDataSetChanged();
mStepsPerMinuteHistoryChart.notifyDataSetChanged();
renderCharts();
private boolean addHistoryDataSet(boolean force) {
if (mStepsPerMinuteHistoryChart.getData() == null) {
// ignore the first default value to keep the "no-data-description" visible
if (force || mSteps.getTotalSteps() > 0) {
LineData data = new LineData();
data.addDataSet(mHistorySet);
data.addDataSet(mHeartRateSet);
mStepsPerMinuteHistoryChart.setData(data);
return true;
}
return false;
}
return true;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
IntentFilter filterLocal = new IntentFilter();
filterLocal.addAction(DeviceService.ACTION_REALTIME_STEPS);
filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
heartRateValues = new ArrayList<>();
tsTranslation = new TimestampTranslation();
View rootView = inflater.inflate(R.layout.fragment_live_activity, container, false);
@ -171,8 +268,8 @@ public class LiveActivityFragment extends AbstractChartFragment {
mTotalStepsChart = (CustomBarChart) rootView.findViewById(R.id.livechart_steps_total);
mStepsPerMinuteHistoryChart = (BarLineChartBase) rootView.findViewById(R.id.livechart_steps_per_minute_history);
totalStepsEntry = new BarEntry(0, 1);
stepsPerMinuteEntry = new BarEntry(0, 1);
totalStepsEntry = new BarEntry(1, 0);
stepsPerMinuteEntry = new BarEntry(1, 0);
mStepsPerMinuteData = setupCurrentChart(mStepsPerMinuteCurrentChart, stepsPerMinuteEntry, getString(R.string.live_activity_current_steps_per_minute));
mTotalStepsData = setupTotalStepsChart(mTotalStepsChart, totalStepsEntry, getString(R.string.live_activity_total_steps));
@ -183,32 +280,112 @@ public class LiveActivityFragment extends AbstractChartFragment {
return rootView;
}
@Override
public void onPause() {
enableRealtimeTracking(false);
super.onPause();
}
@Override
public void onResume() {
super.onResume();
enableRealtimeTracking(true);
}
private ScheduledExecutorService startActivityPulse() {
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
FragmentActivity activity = LiveActivityFragment.this.getActivity();
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
pulse();
}
});
}
}
}, 0, getPulseIntervalMillis(), TimeUnit.MILLISECONDS);
return service;
}
private void stopActivityPulse() {
if (pulseScheduler != null) {
pulseScheduler.shutdownNow();
pulseScheduler = null;
}
}
/**
* Called in the UI thread.
*/
private void pulse() {
addEntries(translateTimestamp(System.currentTimeMillis()));
LineData historyData = (LineData) mStepsPerMinuteHistoryChart.getData();
if (historyData == null) {
return;
}
historyData.notifyDataChanged();
mTotalStepsData.notifyDataSetChanged();
mStepsPerMinuteData.notifyDataSetChanged();
mStepsPerMinuteHistoryChart.notifyDataSetChanged();
renderCharts();
// have to enable it again and again to keep it measureing
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(true);
}
private int getPulseIntervalMillis() {
return 1000;
}
@Override
protected void onMadeVisibleInActivity() {
GBApplication.deviceService().onEnableRealtimeSteps(true);
super.onMadeVisibleInActivity();
if (getActivity() != null) {
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
enableRealtimeTracking(true);
}
private void enableRealtimeTracking(boolean enable) {
if (enable && pulseScheduler != null) {
// already running
return;
}
GBApplication.deviceService().onEnableRealtimeSteps(enable);
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(enable);
if (enable) {
if (getActivity() != null) {
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
pulseScheduler = startActivityPulse();
} else {
stopActivityPulse();
if (getActivity() != null) {
getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
}
@Override
protected void onMadeInvisibleInActivity() {
GBApplication.deviceService().onEnableRealtimeSteps(false);
if (getActivity() != null) {
getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
enableRealtimeTracking(false);
super.onMadeInvisibleInActivity();
}
@Override
public void onDestroyView() {
onMadeInvisibleInActivity();
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mReceiver);
super.onDestroyView();
}
private BarDataSet setupCurrentChart(CustomBarChart chart, BarEntry entry, String title) {
mStepsPerMinuteCurrentChart.getAxisLeft().setAxisMaxValue(300);
mStepsPerMinuteCurrentChart.getAxisLeft().setAxisMaxValue(MAX_STEPS_PER_MINUTE);
return setupCommonChart(chart, entry, title);
}
@ -218,33 +395,35 @@ public class LiveActivityFragment extends AbstractChartFragment {
// chart.getXAxis().setPosition(XAxis.XAxisPosition.TOP);
chart.getXAxis().setDrawLabels(false);
chart.getXAxis().setEnabled(false);
chart.getXAxis().setTextColor(CHART_TEXT_COLOR);
chart.getAxisLeft().setTextColor(CHART_TEXT_COLOR);
chart.setBackgroundColor(BACKGROUND_COLOR);
chart.setDescriptionColor(DESCRIPTION_COLOR);
chart.setDescription(title);
chart.setNoDataTextDescription("");
chart.getDescription().setTextColor(DESCRIPTION_COLOR);
chart.getDescription().setText(title);
// chart.setNoDataTextDescription("");
chart.setNoDataText("");
chart.getAxisRight().setEnabled(false);
List<BarEntry> entries = new ArrayList<>();
List<String> xLabels = new ArrayList<>();
List<Integer> colors = new ArrayList<>();
entries.add(new BarEntry(0,0));
entries.add(new BarEntry(0, 0));
entries.add(entry);
entries.add(new BarEntry(0,2));
entries.add(new BarEntry(2, 0));
colors.add(akActivity.color);
colors.add(akActivity.color);
colors.add(akActivity.color);
//we don't want labels
xLabels.add("");
xLabels.add("");
xLabels.add("");
// //we don't want labels
// xLabels.add("");
// xLabels.add("");
// xLabels.add("");
BarDataSet set = new BarDataSet(entries, "");
set.setDrawValues(false);
set.setColors(colors);
BarData data = new BarData(xLabels, set);
data.setGroupSpace(0);
BarData data = new BarData(set);
// data.setGroupSpace(0);
chart.setData(data);
chart.getLegend().setEnabled(false);
@ -253,16 +432,17 @@ public class LiveActivityFragment extends AbstractChartFragment {
}
private BarDataSet setupTotalStepsChart(CustomBarChart chart, BarEntry entry, String label) {
mTotalStepsChart.getAxisLeft().setAxisMaxValue(5000); // TODO: use daily goal - already reached steps
mTotalStepsChart.getAxisLeft().setAxisMaximum(5000); // TODO: use daily goal - already reached steps
return setupCommonChart(chart, entry, label); // at the moment, these look the same
}
private void setupHistoryChart(BarLineChartBase chart) {
configureBarLineChartDefaults(chart);
chart.setTouchEnabled(false); // no zooming or anything, because it's updated all the time
chart.setBackgroundColor(BACKGROUND_COLOR);
chart.setDescriptionColor(DESCRIPTION_COLOR);
chart.setDescription(getString(R.string.live_activity_steps_per_minute_history));
chart.getDescription().setTextColor(DESCRIPTION_COLOR);
chart.getDescription().setText(getString(R.string.live_activity_steps_per_minute_history));
chart.setNoDataText(getString(R.string.live_activity_start_your_activity));
chart.getLegend().setEnabled(false);
Paint infoPaint = chart.getPaint(Chart.PAINT_INFO);
@ -281,22 +461,28 @@ public class LiveActivityFragment extends AbstractChartFragment {
y.setDrawGridLines(false);
y.setDrawTopYLabelEntry(false);
y.setTextColor(CHART_TEXT_COLOR);
y.setEnabled(true);
y.setAxisMinimum(0);
YAxis yAxisRight = chart.getAxisRight();
yAxisRight.setDrawGridLines(false);
yAxisRight.setEnabled(false);
yAxisRight.setDrawLabels(false);
yAxisRight.setEnabled(true);
yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(false);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaximum(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinimum(HeartRateUtils.MIN_HEART_RATE_VALUE);
mHistorySet = new LineDataSet(new ArrayList<Entry>(), getString(R.string.live_activity_steps_history));
mHistorySet.setAxisDependency(YAxis.AxisDependency.LEFT);
mHistorySet.setColor(akActivity.color);
mHistorySet.setDrawCircles(false);
mHistorySet.setDrawCubic(true);
mHistorySet.setMode(LineDataSet.Mode.CUBIC_BEZIER);
mHistorySet.setDrawFilled(true);
mHistorySet.setDrawValues(false);
mHeartRateSet = createHeartrateSet(new ArrayList<Entry>(), getString(R.string.live_activity_heart_rate));
mHeartRateSet.setDrawValues(false);
}
@Override
@ -316,7 +502,13 @@ public class LiveActivityFragment extends AbstractChartFragment {
}
@Override
protected void refreshInBackground(DBHandler db, GBDevice device) {
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
throw new UnsupportedOperationException();
}
@Override
protected void updateChartsnUIThread(ChartsData chartsData) {
throw new UnsupportedOperationException();
}
@Override

View File

@ -1,6 +1,21 @@
/* Copyright (C) 2015-2017 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.activities.charts;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
@ -24,8 +39,8 @@ public class SingleEntryValueAnimator extends ChartAnimator {
}
public void setEntryYValue(float value) {
this.previousValue = entry.getVal();
entry.setVal(value);
this.previousValue = entry.getY();
entry.setY(value);
}
@Override
@ -39,10 +54,10 @@ public class SingleEntryValueAnimator extends ChartAnimator {
float startAnim;
float endAnim = 1f;
if (entry.getVal() == 0f) {
if (entry.getY() == 0f) {
startAnim = 0f;
} else {
startAnim = previousValue / entry.getVal();
startAnim = previousValue / entry.getY();
}
// LOG.debug("anim factors: " + startAnim + ", " + endAnim);

View File

@ -1,3 +1,20 @@
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.content.Context;
@ -8,15 +25,18 @@ import android.view.View;
import android.view.ViewGroup;
import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.CombinedChart;
import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.components.LegendEntry;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.CombinedData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.formatter.ValueFormatter;
import com.github.mikephil.charting.data.PieEntry;
import com.github.mikephil.charting.formatter.IValueFormatter;
import com.github.mikephil.charting.utils.ViewPortHandler;
import org.slf4j.Logger;
@ -27,10 +47,12 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
@ -38,7 +60,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
public class SleepChartFragment extends AbstractChartFragment {
protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class);
private BarLineChartBase mActivityChart;
private CombinedChart mActivityChart;
private PieChart mSleepAmountChart;
private int mSmartAlarmFrom = -1;
@ -47,41 +69,61 @@ public class SleepChartFragment extends AbstractChartFragment {
private int mSmartAlarmGoneOff = -1;
@Override
protected void refreshInBackground(DBHandler db, GBDevice device) {
List<ActivitySample> samples = getSamples(db, device);
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
List<? extends ActivitySample> samples = getSamples(db, device);
refresh(device, mActivityChart, samples);
refreshSleepAmounts(device, mSleepAmountChart, samples);
MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples);
DefaultChartsData chartsData = refresh(device, samples);
return new MyChartsData(mySleepChartsData, chartsData);
}
private void refreshSleepAmounts(GBDevice mGBDevice, PieChart pieChart, List<ActivitySample> samples) {
private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<? extends ActivitySample> samples) {
ActivityAnalysis analysis = new ActivityAnalysis();
ActivityAmounts amounts = analysis.calculateActivityAmounts(samples);
String totalSleep = DateTimeUtils.formatDurationHoursMinutes(amounts.getTotalSeconds(), TimeUnit.SECONDS);
pieChart.setCenterText(totalSleep);
PieData data = new PieData();
List<Entry> entries = new ArrayList<>();
List<PieEntry> entries = new ArrayList<>();
List<Integer> colors = new ArrayList<>();
int index = 0;
// int index = 0;
long totalSeconds = 0;
for (ActivityAmount amount : amounts.getAmounts()) {
long value = amount.getTotalSeconds();
entries.add(new Entry(amount.getTotalSeconds(), index++));
colors.add(getColorFor(amount.getActivityKind()));
data.addXValue(amount.getName(getActivity()));
if ((amount.getActivityKind() & ActivityKind.TYPE_SLEEP) != 0) {
long value = amount.getTotalSeconds();
totalSeconds += value;
// entries.add(new PieEntry(value, index++));
entries.add(new PieEntry(value, amount.getName(getActivity())));
colors.add(getColorFor(amount.getActivityKind()));
// data.addXValue(amount.getName(getActivity()));
}
}
String totalSleep = DateTimeUtils.formatDurationHoursMinutes(totalSeconds, TimeUnit.SECONDS);
PieDataSet set = new PieDataSet(entries, "");
set.setValueFormatter(new ValueFormatter() {
set.setValueFormatter(new IValueFormatter() {
@Override
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
return DateTimeUtils.formatDurationHoursMinutes((long) value, TimeUnit.SECONDS);
}
});
set.setColors(colors);
set.setValueTextColor(DESCRIPTION_COLOR);
set.setValueTextSize(13f);
set.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
set.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
data.setDataSet(set);
pieChart.setData(data);
pieChart.getLegend().setEnabled(false);
//setupLegend(pieChart);
return new MySleepChartsData(totalSleep, data);
}
@Override
protected void updateChartsnUIThread(ChartsData chartsData) {
MyChartsData mcd = (MyChartsData) chartsData;
mSleepAmountChart.setCenterText(mcd.getPieData().getTotalSleep());
mSleepAmountChart.setData(mcd.getPieData().getPieData());
mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
mActivityChart.getXAxis().setValueFormatter(mcd.getChartsData().getXValueFormatter());
mActivityChart.setData(mcd.getChartsData().getData());
}
@Override
@ -94,7 +136,7 @@ public class SleepChartFragment extends AbstractChartFragment {
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_sleepchart, container, false);
mActivityChart = (BarLineChartBase) rootView.findViewById(R.id.sleepchart);
mActivityChart = (CombinedChart) rootView.findViewById(R.id.sleepchart);
mSleepAmountChart = (PieChart) rootView.findViewById(R.id.sleepchart_pie_light_deep);
setupActivityChart();
@ -123,15 +165,17 @@ public class SleepChartFragment extends AbstractChartFragment {
private void setupSleepAmountChart() {
mSleepAmountChart.setBackgroundColor(BACKGROUND_COLOR);
mSleepAmountChart.setDescriptionColor(DESCRIPTION_COLOR);
mSleepAmountChart.setDescription("");
mSleepAmountChart.setNoDataTextDescription("");
mSleepAmountChart.getDescription().setTextColor(DESCRIPTION_COLOR);
mSleepAmountChart.setEntryLabelColor(DESCRIPTION_COLOR);
mSleepAmountChart.getDescription().setText("");
// mSleepAmountChart.getDescription().setNoDataTextDescription("");
mSleepAmountChart.setNoDataText("");
mSleepAmountChart.getLegend().setEnabled(false);
}
private void setupActivityChart() {
mActivityChart.setBackgroundColor(BACKGROUND_COLOR);
mActivityChart.setDescriptionColor(DESCRIPTION_COLOR);
mActivityChart.getDescription().setTextColor(DESCRIPTION_COLOR);
configureBarLineChartDefaults(mActivityChart);
XAxis x = mActivityChart.getXAxis();
@ -145,7 +189,8 @@ public class SleepChartFragment extends AbstractChartFragment {
y.setDrawGridLines(false);
// y.setDrawLabels(false);
// TODO: make fixed max value optional
y.setAxisMaxValue(1f);
y.setAxisMaximum(1f);
y.setAxisMinimum(0);
y.setDrawTopYLabelEntry(false);
y.setTextColor(CHART_TEXT_COLOR);
@ -154,30 +199,83 @@ public class SleepChartFragment extends AbstractChartFragment {
YAxis yAxisRight = mActivityChart.getAxisRight();
yAxisRight.setDrawGridLines(false);
yAxisRight.setEnabled(false);
yAxisRight.setDrawLabels(false);
yAxisRight.setDrawTopYLabelEntry(false);
yAxisRight.setEnabled(supportsHeartrate(getChartsHost().getDevice()));
yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE);
}
@Override
protected void setupLegend(Chart chart) {
List<Integer> legendColors = new ArrayList<>(2);
List<String> legendLabels = new ArrayList<>(2);
legendColors.add(akLightSleep.color);
legendLabels.add(akLightSleep.label);
legendColors.add(akDeepSleep.color);
legendLabels.add(akDeepSleep.label);
chart.getLegend().setCustom(legendColors, legendLabels);
List<LegendEntry> legendEntries = new ArrayList<>(3);
LegendEntry lightSleepEntry = new LegendEntry();
lightSleepEntry.label = akLightSleep.label;
lightSleepEntry.formColor = akLightSleep.color;
legendEntries.add(lightSleepEntry);
LegendEntry deepSleepEntry = new LegendEntry();
deepSleepEntry.label = akDeepSleep.label;
deepSleepEntry.formColor = akDeepSleep.color;
legendEntries.add(deepSleepEntry);
if (supportsHeartrate(getChartsHost().getDevice())) {
LegendEntry hrEntry = new LegendEntry();
hrEntry.label = HEARTRATE_LABEL;
hrEntry.formColor = HEARTRATE_COLOR;
legendEntries.add(hrEntry);
}
chart.getLegend().setCustom(legendEntries);
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
}
@Override
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
return super.getSleepSamples(db, device, tsFrom, tsTo);
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
// temporary fix for totally wrong sleep amounts
// return super.getSleepSamples(db, device, tsFrom, tsTo);
return super.getAllSamples(db, device, tsFrom, tsTo);
}
@Override
protected void renderCharts() {
mActivityChart.animateX(ANIM_TIME, Easing.EasingOption.EaseInOutQuart);
mSleepAmountChart.invalidate();
}
private static class MySleepChartsData extends ChartsData {
private String totalSleep;
private final PieData pieData;
public MySleepChartsData(String totalSleep, PieData pieData) {
this.totalSleep = totalSleep;
this.pieData = pieData;
}
public PieData getPieData() {
return pieData;
}
public CharSequence getTotalSleep() {
return totalSleep;
}
}
private static class MyChartsData extends ChartsData {
private final DefaultChartsData<CombinedData> chartsData;
private final MySleepChartsData pieData;
public MyChartsData(MySleepChartsData pieData, DefaultChartsData<CombinedData> chartsData) {
this.pieData = pieData;
this.chartsData = chartsData;
}
public MySleepChartsData getPieData() {
return pieData;
}
public DefaultChartsData<CombinedData> getChartsData() {
return chartsData;
}
}
}

View File

@ -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/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
@ -6,7 +22,7 @@ public class SleepUtils {
public static final float Y_VALUE_DEEP_SLEEP = 0.01f;
public static final float Y_VALUE_LIGHT_SLEEP = 0.016f;
public static final boolean isSleep(byte type) {
public static boolean isSleep(byte type) {
return type == ActivityKind.TYPE_DEEP_SLEEP || type == ActivityKind.TYPE_LIGHT_SLEEP;
}
}

View File

@ -0,0 +1,175 @@
/* 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 right = mStatsChart.getXAxis(); //believe it or not, the X axis is vertical for HorizontalBarChart
right.setTextColor(CHART_TEXT_COLOR);
YAxis bottom = mStatsChart.getAxisRight();
bottom.setTextColor(CHART_TEXT_COLOR);
bottom.setGranularity(1f);
YAxis top = mStatsChart.getAxisLeft();
top.setTextColor(CHART_TEXT_COLOR);
top.setGranularity(1f);
}
@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;
}
}
}

View File

@ -0,0 +1,51 @@
/* Copyright (C) 2016-2017 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.activities.charts;
import com.github.mikephil.charting.components.AxisBase;
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
public class TimestampValueFormatter implements IAxisValueFormatter {
private final Calendar cal;
// private DateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
private DateFormat dateFormat;
public TimestampValueFormatter() {
this(new SimpleDateFormat("HH:mm"));
}
public TimestampValueFormatter(DateFormat dateFormat) {
this.dateFormat = dateFormat;
cal = GregorianCalendar.getInstance();
cal.clear();
}
@Override
public String getFormattedValue(float value, AxisBase axis) {
cal.setTimeInMillis((int) value * 1000L);
Date date = cal.getTime();
String dateString = dateFormat.format(date);
return dateString;
}
}

View File

@ -0,0 +1,55 @@
/* Copyright (C) 2016-2017 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.activities.charts;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
public class TrailingActivitySample extends AbstractActivitySample {
private int timestamp;
private long userId;
private long deviceId;
@Override
public void setTimestamp(int timestamp) {
this.timestamp = timestamp;
}
@Override
public void setUserId(long userId) {
this.userId = userId;
}
@Override
public void setDeviceId(long deviceId) {
this.deviceId = deviceId;
}
@Override
public long getDeviceId() {
return deviceId;
}
@Override
public long getUserId() {
return userId;
}
@Override
public int getTimestamp() {
return timestamp;
}
}

View File

@ -0,0 +1,136 @@
/* Copyright (C) 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.activities.charts;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.components.AxisBase;
import com.github.mikephil.charting.components.LegendEntry;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
import com.github.mikephil.charting.formatter.IValueFormatter;
import com.github.mikephil.charting.utils.ViewPortHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
public class WeekSleepChartFragment extends AbstractWeekChartFragment {
@Override
public String getTitle() {
return getString(R.string.weeksleepchart_sleep_a_week);
}
@Override
String getPieDescription(int targetValue) {
return getString(R.string.weeksleepchart_today_sleep_description, DateTimeUtils.formatDurationHoursMinutes(targetValue, TimeUnit.MINUTES));
}
@Override
int getGoal() {
return GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_SLEEP_DURATION, 8) * 60;
}
@Override
int getOffsetHours() {
return -12;
}
@Override
float[] getTotalsForActivityAmounts(ActivityAmounts activityAmounts) {
long totalSecondsDeepSleep = 0;
long totalSecondsLightSleep = 0;
for (ActivityAmount amount : activityAmounts.getAmounts()) {
if (amount.getActivityKind() == ActivityKind.TYPE_DEEP_SLEEP) {
totalSecondsDeepSleep += amount.getTotalSeconds();
} else if (amount.getActivityKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
totalSecondsLightSleep += amount.getTotalSeconds();
}
}
return new float[]{(int) (totalSecondsDeepSleep / 60), (int) (totalSecondsLightSleep / 60)};
}
@Override
protected String formatPieValue(int value) {
return DateTimeUtils.formatDurationHoursMinutes((long) value, TimeUnit.MINUTES);
}
@Override
String[] getPieLabels() {
return new String[]{getString(R.string.abstract_chart_fragment_kind_deep_sleep), getString(R.string.abstract_chart_fragment_kind_light_sleep)};
}
@Override
IValueFormatter getPieValueFormatter() {
return new IValueFormatter() {
@Override
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
return formatPieValue((int) value);
}
};
}
@Override
IValueFormatter getBarValueFormatter() {
return new IValueFormatter() {
@Override
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
return DateTimeUtils.minutesToHHMM((int) value);
}
};
}
@Override
IAxisValueFormatter getYAxisFormatter() {
return new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
return DateTimeUtils.minutesToHHMM((int) value);
}
};
}
@Override
int[] getColors() {
return new int[]{akDeepSleep.color, akLightSleep.color};
}
@Override
protected void setupLegend(Chart chart) {
List<LegendEntry> legendEntries = new ArrayList<>(2);
LegendEntry lightSleepEntry = new LegendEntry();
lightSleepEntry.label = akLightSleep.label;
lightSleepEntry.formColor = akLightSleep.color;
legendEntries.add(lightSleepEntry);
LegendEntry deepSleepEntry = new LegendEntry();
deepSleepEntry.label = akDeepSleep.label;
deepSleepEntry.formColor = akDeepSleep.color;
legendEntries.add(deepSleepEntry);
chart.getLegend().setCustom(legendEntries);
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
}
}

View File

@ -1,225 +1,96 @@
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.components.LimitLine;
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 com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.data.PieDataSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
import com.github.mikephil.charting.formatter.IValueFormatter;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
public class WeekStepsChartFragment extends AbstractChartFragment {
protected static final Logger LOG = LoggerFactory.getLogger(WeekStepsChartFragment.class);
private Locale mLocale;
private int mTargetSteps = 10000;
private BarLineChartBase mWeekStepsChart;
private PieChart mTodayStepsChart;
@Override
protected void refreshInBackground(DBHandler db, GBDevice device) {
ChartsHost chartsHost = getChartsHost();
if (chartsHost != null) {
Calendar day = Calendar.getInstance();
day.setTime(chartsHost.getEndDate());
//NB: we could have omitted the day, but this way we can move things to the past easily
refreshDaySteps(db, mTodayStepsChart, day, device);
refreshWeekBeforeSteps(db, mWeekStepsChart, day, device);
}
}
@Override
protected void renderCharts() {
mWeekStepsChart.invalidate();
mTodayStepsChart.invalidate();
}
private void refreshWeekBeforeSteps(DBHandler db, BarLineChartBase barChart, Calendar day, GBDevice device) {
ActivityAnalysis analysis = new ActivityAnalysis();
day = (Calendar) day.clone(); // do not modify the caller's argument
day.add(Calendar.DATE, -7);
List<BarEntry> entries = new ArrayList<>();
List<String> labels = new ArrayList<>();
for (int counter = 0; counter < 7; counter++) {
entries.add(new BarEntry(analysis.calculateTotalSteps(getSamplesOfDay(db, day, device)), counter));
labels.add(day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale));
day.add(Calendar.DATE, 1);
}
BarDataSet set = new BarDataSet(entries, "");
set.setColor(akActivity.color);
BarData data = new BarData(labels, set);
data.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false);
LimitLine target = new LimitLine(mTargetSteps);
barChart.getAxisLeft().getLimitLines().clear();
barChart.getAxisLeft().addLimitLine(target);
setupLegend(barChart);
barChart.setData(data);
barChart.getLegend().setEnabled(false);
}
private void refreshDaySteps(DBHandler db, PieChart pieChart, Calendar day, GBDevice device) {
ActivityAnalysis analysis = new ActivityAnalysis();
int totalSteps = analysis.calculateTotalSteps(getSamplesOfDay(db, day, device));
pieChart.setCenterText(NumberFormat.getNumberInstance(mLocale).format(totalSteps));
PieData data = new PieData();
List<Entry> entries = new ArrayList<>();
List<Integer> colors = new ArrayList<>();
entries.add(new Entry(totalSteps, 0));
colors.add(akActivity.color);
//we don't want labels on the pie chart
data.addXValue("");
if (totalSteps < mTargetSteps) {
entries.add(new Entry((mTargetSteps - totalSteps), 1));
colors.add(Color.GRAY);
//we don't want labels on the pie chart
data.addXValue("");
}
PieDataSet set = new PieDataSet(entries, "");
set.setColors(colors);
data.setDataSet(set);
//this hides the values (numeric) added to the set. These would be shown aside the strings set with addXValue above
data.setDrawValues(false);
pieChart.setData(data);
pieChart.getLegend().setEnabled(false);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mLocale = getResources().getConfiguration().locale;
View rootView = inflater.inflate(R.layout.fragment_sleepchart, container, false);
GBDevice device = getChartsHost().getDevice();
if (device != null) {
// TODO: eek, this is device specific!
mTargetSteps = MiBandCoordinator.getFitnessGoal(device.getAddress());
}
mWeekStepsChart = (BarLineChartBase) rootView.findViewById(R.id.sleepchart);
mTodayStepsChart = (PieChart) rootView.findViewById(R.id.sleepchart_pie_light_deep);
setupWeekStepsChart();
setupTodayStepsChart();
// refresh immediately instead of use refreshIfVisible(), for perceived performance
refresh();
return rootView;
}
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
public class WeekStepsChartFragment extends AbstractWeekChartFragment {
@Override
public String getTitle() {
return getString(R.string.weekstepschart_steps_a_week);
}
private void setupTodayStepsChart() {
mTodayStepsChart.setBackgroundColor(BACKGROUND_COLOR);
mTodayStepsChart.setDescriptionColor(DESCRIPTION_COLOR);
mTodayStepsChart.setDescription(getContext().getString(R.string.weeksteps_today_steps_description, mTargetSteps));
mTodayStepsChart.setNoDataTextDescription("");
mTodayStepsChart.setNoDataText("");
}
private void setupWeekStepsChart() {
mWeekStepsChart.setBackgroundColor(BACKGROUND_COLOR);
mWeekStepsChart.setDescriptionColor(DESCRIPTION_COLOR);
mWeekStepsChart.setDescription("");
configureBarLineChartDefaults(mWeekStepsChart);
XAxis x = mWeekStepsChart.getXAxis();
x.setDrawLabels(true);
x.setDrawGridLines(false);
x.setEnabled(true);
x.setTextColor(CHART_TEXT_COLOR);
x.setDrawLimitLinesBehindData(true);
YAxis y = mWeekStepsChart.getAxisLeft();
y.setDrawGridLines(false);
y.setDrawTopYLabelEntry(false);
y.setTextColor(CHART_TEXT_COLOR);
y.setEnabled(true);
YAxis yAxisRight = mWeekStepsChart.getAxisRight();
yAxisRight.setDrawGridLines(false);
yAxisRight.setEnabled(false);
yAxisRight.setDrawLabels(false);
yAxisRight.setDrawTopYLabelEntry(false);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
}
protected void setupLegend(Chart chart) {
List<Integer> legendColors = new ArrayList<>(1);
List<String> legendLabels = new ArrayList<>(1);
legendColors.add(akActivity.color);
legendLabels.add(getContext().getString(R.string.chart_steps));
chart.getLegend().setCustom(legendColors, legendLabels);
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
}
private List<ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, GBDevice device) {
int startTs;
int endTs;
day = (Calendar) day.clone(); // do not modify the caller's argument
day.set(Calendar.HOUR_OF_DAY, 0);
day.set(Calendar.MINUTE, 0);
day.set(Calendar.SECOND, 0);
startTs = (int) (day.getTimeInMillis() / 1000);
day.set(Calendar.HOUR_OF_DAY, 23);
day.set(Calendar.MINUTE, 59);
day.set(Calendar.SECOND, 59);
endTs = (int) (day.getTimeInMillis() / 1000);
return getSamples(db, device, startTs, endTs);
@Override
String getPieDescription(int targetValue) {
return getString(R.string.weeksteps_today_steps_description, String.valueOf(targetValue));
}
@Override
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
return super.getAllSamples(db, device, tsFrom, tsTo);
int getGoal() {
return GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, 10000);
}
@Override
int getOffsetHours() {
return 0;
}
@Override
float[] getTotalsForActivityAmounts(ActivityAmounts activityAmounts) {
int totalSteps = 0;
for (ActivityAmount amount : activityAmounts.getAmounts()) {
totalSteps += amount.getTotalSteps();
amount.getTotalSteps();
}
return new float[]{totalSteps};
}
@Override
protected String formatPieValue(int value) {
return String.valueOf(value);
}
@Override
String[] getPieLabels() {
return new String[]{""};
}
@Override
IValueFormatter getPieValueFormatter() {
return null;
}
@Override
IValueFormatter getBarValueFormatter() {
return null;
}
@Override
IAxisValueFormatter getYAxisFormatter() {
return null;
}
@Override
int[] getColors() {
return new int[]{akActivity.color};
}
@Override
protected void setupLegend(Chart chart) {
// no legend here, it is all about the steps here
chart.getLegend().setEnabled(false);
}
}

View File

@ -0,0 +1,189 @@
/* Copyright (C) 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.adapter;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapter.AppBLViewHolder> implements Filterable {
private List<ApplicationInfo> applicationInfoList;
private final int mLayoutId;
private final Context mContext;
private final PackageManager mPm;
private final IdentityHashMap<ApplicationInfo, String> mNameMap;
private ApplicationFilter applicationFilter;
public AppBlacklistAdapter(int layoutId, Context context) {
mLayoutId = layoutId;
mContext = context;
mPm = context.getPackageManager();
applicationInfoList = mPm.getInstalledApplications(PackageManager.GET_META_DATA);
// sort the package list by label and blacklist status
mNameMap = new IdentityHashMap<>(applicationInfoList.size());
for (ApplicationInfo ai : applicationInfoList) {
CharSequence name = mPm.getApplicationLabel(ai);
if (name == null) {
name = ai.packageName;
}
if (GBApplication.appIsBlacklisted(ai.packageName)) {
// sort blacklisted first by prefixing with a '!'
name = "!" + name;
}
mNameMap.put(ai, name.toString());
}
Collections.sort(applicationInfoList, new Comparator<ApplicationInfo>() {
@Override
public int compare(ApplicationInfo ai1, ApplicationInfo ai2) {
final String s1 = mNameMap.get(ai1);
final String s2 = mNameMap.get(ai2);
return s1.compareTo(s2);
}
});
}
@Override
public AppBlacklistAdapter.AppBLViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(mLayoutId, parent, false);
return new AppBLViewHolder(view);
}
@Override
public void onBindViewHolder(AppBlacklistAdapter.AppBLViewHolder holder, int position) {
final ApplicationInfo appInfo = applicationInfoList.get(position);
holder.deviceAppVersionAuthorLabel.setText(appInfo.packageName);
holder.deviceAppNameLabel.setText(mNameMap.get(appInfo));
holder.deviceImageView.setImageDrawable(appInfo.loadIcon(mPm));
holder.checkbox.setChecked(GBApplication.appIsBlacklisted(appInfo.packageName));
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
CheckBox checkBox = ((CheckBox) v.findViewById(R.id.item_checkbox));
checkBox.toggle();
if (checkBox.isChecked()) {
GBApplication.addAppToBlacklist(appInfo.packageName);
} else {
GBApplication.removeFromAppsBlacklist(appInfo.packageName);
}
}
});
}
@Override
public int getItemCount() {
return applicationInfoList.size();
}
@Override
public Filter getFilter() {
if (applicationFilter == null)
applicationFilter = new ApplicationFilter(this, applicationInfoList);
return applicationFilter;
}
public class AppBLViewHolder extends RecyclerView.ViewHolder {
final CheckBox checkbox;
final ImageView deviceImageView;
final TextView deviceAppVersionAuthorLabel;
final TextView deviceAppNameLabel;
AppBLViewHolder(View itemView) {
super(itemView);
checkbox = (CheckBox) itemView.findViewById(R.id.item_checkbox);
deviceImageView = (ImageView) itemView.findViewById(R.id.item_image);
deviceAppVersionAuthorLabel = (TextView) itemView.findViewById(R.id.item_details);
deviceAppNameLabel = (TextView) itemView.findViewById(R.id.item_name);
}
}
private class ApplicationFilter extends Filter {
private final AppBlacklistAdapter adapter;
private final List<ApplicationInfo> originalList;
private final List<ApplicationInfo> filteredList;
private ApplicationFilter(AppBlacklistAdapter adapter, List<ApplicationInfo> originalList) {
super();
this.originalList = new ArrayList<>(originalList);
this.filteredList = new ArrayList<>();
this.adapter = adapter;
}
@Override
protected Filter.FilterResults performFiltering(CharSequence filter) {
filteredList.clear();
final Filter.FilterResults results = new Filter.FilterResults();
if (filter == null || filter.length() == 0)
filteredList.addAll(originalList);
else {
final String filterPattern = filter.toString().toLowerCase().trim();
for (ApplicationInfo ai : originalList) {
CharSequence name = mPm.getApplicationLabel(ai);
if (name.toString().contains(filterPattern) ||
(ai.packageName.contains(filterPattern))) {
filteredList.add(ai);
}
}
}
results.values = filteredList;
results.count = filteredList.size();
return results;
}
@Override
protected void publishResults(CharSequence charSequence, Filter.FilterResults filterResults) {
adapter.applicationInfoList.clear();
adapter.applicationInfoList.addAll((List<ApplicationInfo>) filterResults.values);
adapter.notifyDataSetChanged();
}
}
}

View File

@ -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/>. */
package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.content.Context;
@ -15,6 +31,9 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
/**
* Adapter for displaying GBDeviceCandate instances.
*/
public class DeviceCandidateAdapter extends ArrayAdapter<GBDeviceCandidate> {
private final Context context;
@ -42,17 +61,7 @@ public class DeviceCandidateAdapter extends ArrayAdapter<GBDeviceCandidate> {
String name = formatDeviceCandidate(device);
deviceNameLabel.setText(name);
deviceAddressLabel.setText(device.getMacAddress());
switch (device.getDeviceType()) {
case PEBBLE:
deviceImageView.setImageResource(R.drawable.ic_device_pebble);
break;
case MIBAND:
deviceImageView.setImageResource(R.drawable.ic_device_miband);
break;
default:
deviceImageView.setImageResource(R.drawable.ic_launcher);
}
deviceImageView.setImageResource(device.getDeviceType().getIcon());
return view;
}

View File

@ -1,18 +1,37 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.content.Context;
import android.graphics.Color;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckedTextView;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.R;
@ -20,25 +39,23 @@ import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
public class GBAlarmListAdapter extends ArrayAdapter<GBAlarm> {
/**
* Adapter for displaying GBAlarm instances.
*/
public class GBAlarmListAdapter extends RecyclerView.Adapter<GBAlarmListAdapter.ViewHolder> {
private final Context mContext;
private ArrayList<GBAlarm> alarmList;
public GBAlarmListAdapter(Context context, ArrayList<GBAlarm> alarmList) {
super(context, 0, alarmList);
private List<GBAlarm> alarmList;
public GBAlarmListAdapter(Context context, List<GBAlarm> alarmList) {
this.mContext = context;
this.alarmList = alarmList;
}
public GBAlarmListAdapter(Context context, Set<String> preferencesAlarmListSet) {
super(context, 0, new ArrayList<GBAlarm>());
this.mContext = context;
alarmList = new ArrayList<GBAlarm>();
alarmList = new ArrayList<>();
for (String alarmString : preferencesAlarmListSet) {
alarmList.add(new GBAlarm(alarmString));
@ -47,18 +64,21 @@ public class GBAlarmListAdapter extends ArrayAdapter<GBAlarm> {
Collections.sort(alarmList);
}
public void setAlarmList(Set<String> preferencesAlarmListSet) {
alarmList = new ArrayList<GBAlarm>();
public void setAlarmList(Set<String> preferencesAlarmListSet, int reservedSlots) {
alarmList = new ArrayList<>();
for (String alarmString : preferencesAlarmListSet) {
alarmList.add(new GBAlarm(alarmString));
}
Collections.sort(alarmList);
//cannot do this earlier because the Set is not guaranteed to be in order by ID
alarmList.subList(alarmList.size() - reservedSlots, alarmList.size()).clear();
}
public ArrayList<? extends Alarm> getAlarmList() {
return alarmList;
return (ArrayList) alarmList;
}
@ -72,53 +92,26 @@ public class GBAlarmListAdapter extends ArrayAdapter<GBAlarm> {
}
@Override
public int getCount() {
if (alarmList != null) {
return alarmList.size();
}
return 0;
public GBAlarmListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_alarm, parent, false);
ViewHolder vh = new ViewHolder(view);
return vh;
}
@Override
public GBAlarm getItem(int position) {
if (alarmList != null) {
return alarmList.get(position);
}
return null;
}
public void onBindViewHolder(ViewHolder holder, final int position) {
@Override
public long getItemId(int position) {
if (alarmList != null) {
return alarmList.get(position).getIndex();
}
return 0;
}
final GBAlarm alarm = alarmList.get(position);
@Override
public View getView(int position, View view, ViewGroup parent) {
holder.alarmDayMonday.setChecked(alarm.getRepetition(Alarm.ALARM_MON));
holder.alarmDayTuesday.setChecked(alarm.getRepetition(Alarm.ALARM_TUE));
holder.alarmDayWednesday.setChecked(alarm.getRepetition(Alarm.ALARM_WED));
holder.alarmDayThursday.setChecked(alarm.getRepetition(Alarm.ALARM_THU));
holder.alarmDayFriday.setChecked(alarm.getRepetition(Alarm.ALARM_FRI));
holder.alarmDaySaturday.setChecked(alarm.getRepetition(Alarm.ALARM_SAT));
holder.alarmDaySunday.setChecked(alarm.getRepetition(Alarm.ALARM_SUN));
final GBAlarm alarm = getItem(position);
if (view == null) {
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.alarm_item, parent, false);
}
TextView alarmTime = (TextView) view.findViewById(R.id.alarm_item_time);
Switch isEnabled = (Switch) view.findViewById(R.id.alarm_item_toggle);
TextView isSmartWakeup = (TextView) view.findViewById(R.id.alarm_smart_wakeup);
highlightDay((TextView) view.findViewById(R.id.alarm_item_sunday), alarm.getRepetition(Alarm.ALARM_SUN));
highlightDay((TextView) view.findViewById(R.id.alarm_item_monday), alarm.getRepetition(Alarm.ALARM_MON));
highlightDay((TextView) view.findViewById(R.id.alarm_item_tuesday), alarm.getRepetition(Alarm.ALARM_TUE));
highlightDay((TextView) view.findViewById(R.id.alarm_item_wednesday), alarm.getRepetition(Alarm.ALARM_WED));
highlightDay((TextView) view.findViewById(R.id.alarm_item_thursday), alarm.getRepetition(Alarm.ALARM_THU));
highlightDay((TextView) view.findViewById(R.id.alarm_item_friday), alarm.getRepetition(Alarm.ALARM_FRI));
highlightDay((TextView) view.findViewById(R.id.alarm_item_saturday), alarm.getRepetition(Alarm.ALARM_SAT));
isEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
holder.isEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
alarm.setEnabled(isChecked);
@ -126,28 +119,62 @@ public class GBAlarmListAdapter extends ArrayAdapter<GBAlarm> {
}
});
view.setOnClickListener(new View.OnClickListener() {
holder.container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((ConfigureAlarms) mContext).configureAlarm(alarm);
}
});
alarmTime.setText(alarm.getTime());
isEnabled.setChecked(alarm.isEnabled());
holder.alarmTime.setText(alarm.getTime());
holder.isEnabled.setChecked(alarm.isEnabled());
if (alarm.isSmartWakeup()) {
isSmartWakeup.setVisibility(TextView.VISIBLE);
holder.isSmartWakeup.setVisibility(TextView.VISIBLE);
} else {
isSmartWakeup.setVisibility(TextView.GONE);
}
return view;
}
private void highlightDay(TextView view, boolean isOn) {
if (isOn) {
view.setTextColor(Color.BLUE);
} else {
view.setTextColor(Color.BLACK);
holder.isSmartWakeup.setVisibility(TextView.GONE);
}
}
@Override
public int getItemCount() {
return alarmList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
CardView container;
TextView alarmTime;
Switch isEnabled;
TextView isSmartWakeup;
CheckedTextView alarmDayMonday;
CheckedTextView alarmDayTuesday;
CheckedTextView alarmDayWednesday;
CheckedTextView alarmDayThursday;
CheckedTextView alarmDayFriday;
CheckedTextView alarmDaySaturday;
CheckedTextView alarmDaySunday;
ViewHolder(View view) {
super(view);
container = (CardView) view.findViewById(R.id.card_view);
alarmTime = (TextView) view.findViewById(R.id.alarm_item_time);
isEnabled = (Switch) view.findViewById(R.id.alarm_item_toggle);
isSmartWakeup = (TextView) view.findViewById(R.id.alarm_smart_wakeup);
alarmDayMonday = (CheckedTextView) view.findViewById(R.id.alarm_item_monday);
alarmDayTuesday = (CheckedTextView) view.findViewById(R.id.alarm_item_tuesday);
alarmDayWednesday = (CheckedTextView) view.findViewById(R.id.alarm_item_wednesday);
alarmDayThursday = (CheckedTextView) view.findViewById(R.id.alarm_item_thursday);
alarmDayFriday = (CheckedTextView) view.findViewById(R.id.alarm_item_friday);
alarmDaySaturday = (CheckedTextView) view.findViewById(R.id.alarm_item_saturday);
alarmDaySunday = (CheckedTextView) view.findViewById(R.id.alarm_item_sunday);
}
}
}

View File

@ -1,93 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.content.Context;
import android.graphics.Color;
import android.support.v4.content.ContextCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
public class GBDeviceAdapter extends ArrayAdapter<GBDevice> {
private final Context context;
public GBDeviceAdapter(Context context, List<GBDevice> deviceList) {
super(context, 0, deviceList);
this.context = context;
}
@Override
public View getView(int position, View view, ViewGroup parent) {
GBDevice device = getItem(position);
if (view == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.device_item, parent, false);
}
TextView deviceStatusLabel = (TextView) view.findViewById(R.id.device_status);
TextView deviceNameLabel = (TextView) view.findViewById(R.id.device_name);
TextView deviceInfoLabel = (TextView) view.findViewById(R.id.device_info);
TextView batteryStatusLabel = (TextView) view.findViewById(R.id.battery_status);
ImageView deviceImageView = (ImageView) view.findViewById(R.id.device_image);
ProgressBar busyIndicator = (ProgressBar) view.findViewById(R.id.device_busy_indicator);
deviceNameLabel.setText(device.getName());
deviceInfoLabel.setText(device.getInfoString());
if (device.isBusy()) {
deviceStatusLabel.setText(device.getBusyTask());
busyIndicator.setVisibility(View.VISIBLE);
batteryStatusLabel.setVisibility(View.GONE);
deviceInfoLabel.setVisibility(View.GONE);
} else {
deviceStatusLabel.setText(device.getStateString());
busyIndicator.setVisibility(View.GONE);
batteryStatusLabel.setVisibility(View.VISIBLE);
deviceInfoLabel.setVisibility(View.VISIBLE);
}
short batteryLevel = device.getBatteryLevel();
if (batteryLevel != GBDevice.BATTERY_UNKNOWN) {
batteryStatusLabel.setText("BAT: " + device.getBatteryLevel() + "%");
BatteryState batteryState = device.getBatteryState();
if (BatteryState.BATTERY_LOW.equals(batteryState)) {
batteryStatusLabel.setTextColor(Color.RED);
} else {
batteryStatusLabel.setTextColor(ContextCompat.getColor(getContext(), R.color.secondarytext));
if (BatteryState.BATTERY_CHARGING.equals(batteryState) ||
BatteryState.BATTERY_CHARGING_FULL.equals(batteryState)) {
batteryStatusLabel.append(" CHG");
}
}
} else {
batteryStatusLabel.setText("");
}
switch (device.getType()) {
case PEBBLE:
deviceImageView.setImageResource(R.drawable.ic_device_pebble);
break;
case MIBAND:
deviceImageView.setImageResource(R.drawable.ic_device_miband);
break;
default:
deviceImageView.setImageResource(R.drawable.ic_launcher);
}
return view;
}
}

View File

@ -0,0 +1,459 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.support.design.widget.Snackbar;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.transition.TransitionManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.AudioSettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
/**
* Adapter for displaying GBDevice instances.
*/
public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.ViewHolder> {
private final Context context;
private List<GBDevice> deviceList;
private int expandedDevicePosition = RecyclerView.NO_POSITION;
private ViewGroup parent;
public GBDeviceAdapterv2(Context context, List<GBDevice> deviceList) {
this.context = context;
this.deviceList = deviceList;
}
@Override
public GBDeviceAdapterv2.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
this.parent = parent;
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.device_itemv2, parent, false);
ViewHolder vh = new ViewHolder(view);
return vh;
}
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
final GBDevice device = deviceList.get(position);
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
holder.container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (device.isInitialized() || device.isConnected()) {
showTransientSnackbar(R.string.controlcenter_snackbar_need_longpress);
} else {
showTransientSnackbar(R.string.controlcenter_snackbar_connecting);
GBApplication.deviceService().connect(device);
}
}
});
holder.container.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (device.getState() != GBDevice.State.NOT_CONNECTED) {
showTransientSnackbar(R.string.controlcenter_snackbar_disconnecting);
GBApplication.deviceService().disconnect();
}
return true;
}
});
holder.deviceImageView.setImageResource(R.drawable.level_list_device);
//level-list does not allow negative values, hence we always add 100 to the key.
holder.deviceImageView.setImageLevel(device.getType().getKey() + 100 + (device.isInitialized() ? 100 : 0));
holder.deviceNameLabel.setText(getUniqueDeviceName(device));
if (device.isBusy()) {
holder.deviceStatusLabel.setText(device.getBusyTask());
holder.busyIndicator.setVisibility(View.VISIBLE);
} else {
holder.deviceStatusLabel.setText(device.getStateString());
holder.busyIndicator.setVisibility(View.INVISIBLE);
}
//begin of action row
//battery
holder.batteryStatusBox.setVisibility(View.GONE);
short batteryLevel = device.getBatteryLevel();
if (batteryLevel != GBDevice.BATTERY_UNKNOWN) {
holder.batteryStatusBox.setVisibility(View.VISIBLE);
holder.batteryStatusLabel.setText(device.getBatteryLevel() + "%");
BatteryState batteryState = device.getBatteryState();
if (BatteryState.BATTERY_CHARGING.equals(batteryState) ||
BatteryState.BATTERY_CHARGING_FULL.equals(batteryState)) {
holder.batteryIcon.setImageLevel(device.getBatteryLevel() + 100);
} else {
holder.batteryIcon.setImageLevel(device.getBatteryLevel());
}
}
//fetch activity data
holder.fetchActivityDataBox.setVisibility((device.isInitialized() && coordinator.supportsActivityDataFetching()) ? View.VISIBLE : View.GONE);
holder.fetchActivityData.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
showTransientSnackbar(R.string.busy_task_fetch_activity_data);
GBApplication.deviceService().onFetchActivityData();
}
}
);
//take screenshot
holder.takeScreenshotView.setVisibility((device.isInitialized() && coordinator.supportsScreenshots()) ? View.VISIBLE : View.GONE);
holder.takeScreenshotView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
showTransientSnackbar(R.string.controlcenter_snackbar_requested_screenshot);
GBApplication.deviceService().onScreenshotReq();
}
}
);
//manage apps
holder.manageAppsView.setVisibility((device.isInitialized() && coordinator.supportsAppsManagement()) ? View.VISIBLE : View.GONE);
holder.manageAppsView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
Class<? extends Activity> appsManagementActivity = coordinator.getAppsManagementActivity();
if (appsManagementActivity != null) {
Intent startIntent = new Intent(context, appsManagementActivity);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent);
}
}
}
);
//set alarms
holder.setAlarmsView.setVisibility(coordinator.supportsAlarmConfiguration() ? View.VISIBLE : View.GONE);
holder.setAlarmsView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
Intent startIntent;
startIntent = new Intent(context, ConfigureAlarms.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent);
}
}
);
//show graphs
holder.showActivityGraphs.setVisibility(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE);
holder.showActivityGraphs.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
Intent startIntent;
startIntent = new Intent(context, ChartsActivity.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent);
}
}
);
// audio settings
holder.showAudioSettings.setVisibility(device.isInitialized() && coordinator.supportsAudioSettings() ? View.VISIBLE : View.GONE);
holder.showAudioSettings.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showTransientSnackbar(R.string.controlcenter_snackbar_requested_screenshot);
Intent startIntent;
startIntent = new Intent(context, AudioSettingsActivity.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent);
}
}
);
ItemWithDetailsAdapter infoAdapter = new ItemWithDetailsAdapter(context, device.getDeviceInfos());
infoAdapter.setHorizontalAlignment(true);
holder.deviceInfoList.setAdapter(infoAdapter);
justifyListViewHeightBasedOnChildren(holder.deviceInfoList);
holder.deviceInfoList.setFocusable(false);
final boolean detailsShown = position == expandedDevicePosition;
boolean showInfoIcon = device.hasDeviceInfos() && !device.isBusy();
holder.deviceInfoView.setVisibility(showInfoIcon ? View.VISIBLE : View.GONE);
holder.deviceInfoBox.setActivated(detailsShown);
holder.deviceInfoBox.setVisibility(detailsShown ? View.VISIBLE : View.GONE);
holder.deviceInfoView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
expandedDevicePosition = detailsShown ? -1 : position;
TransitionManager.beginDelayedTransition(parent);
notifyDataSetChanged();
}
}
);
holder.findDevice.setVisibility((device.isInitialized() &&
device.getType() != DeviceType.HERE) ? View.VISIBLE : View.GONE);
holder.findDevice.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
if (device.getType() == DeviceType.VIBRATISSIMO) {
Intent startIntent;
startIntent = new Intent(context, VibrationActivity.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent);
return;
}
GBApplication.deviceService().onFindDevice(true);
//TODO: extract string resource if we like this solution.
Snackbar.make(parent, R.string.control_center_find_lost_device, Snackbar.LENGTH_INDEFINITE).setAction("Found it!", new View.OnClickListener() {
@Override
public void onClick(View v) {
GBApplication.deviceService().onFindDevice(false);
}
}).setCallback(new Snackbar.Callback() {
@Override
public void onDismissed(Snackbar snackbar, int event) {
GBApplication.deviceService().onFindDevice(false);
super.onDismissed(snackbar, event);
}
}).show();
// ProgressDialog.show(
// context,
// context.getString(R.string.control_center_find_lost_device),
// context.getString(R.string.control_center_cancel_to_stop_vibration),
// true, true,
// new DialogInterface.OnCancelListener() {
// @Override
// public void onCancel(DialogInterface dialog) {
// GBApplication.deviceService().onFindDevice(false);
// }
// });
}
}
);
//remove device, hidden under details
holder.removeDevice.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
new AlertDialog.Builder(context)
.setCancelable(true)
.setTitle(context.getString(R.string.controlcenter_delete_device_name, device.getName()))
.setMessage(R.string.controlcenter_delete_device_dialogmessage)
.setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
if (coordinator != null) {
coordinator.deleteDevice(device);
}
DeviceHelper.getInstance().removeBond(device);
} catch (Exception ex) {
GB.toast(context, "Error deleting device: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
} finally {
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
}
}
})
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// do nothing
}
})
.show();
}
});
}
@Override
public int getItemCount() {
return deviceList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
CardView container;
ImageView deviceImageView;
TextView deviceNameLabel;
TextView deviceStatusLabel;
//actions
LinearLayout batteryStatusBox;
TextView batteryStatusLabel;
ImageView batteryIcon;
LinearLayout fetchActivityDataBox;
ImageView fetchActivityData;
ProgressBar busyIndicator;
ImageView takeScreenshotView;
ImageView manageAppsView;
ImageView setAlarmsView;
ImageView showActivityGraphs;
ImageView showAudioSettings;
ImageView deviceInfoView;
//overflow
final RelativeLayout deviceInfoBox;
ListView deviceInfoList;
ImageView findDevice;
ImageView removeDevice;
ViewHolder(View view) {
super(view);
container = (CardView) view.findViewById(R.id.card_view);
deviceImageView = (ImageView) view.findViewById(R.id.device_image);
deviceNameLabel = (TextView) view.findViewById(R.id.device_name);
deviceStatusLabel = (TextView) view.findViewById(R.id.device_status);
//actions
batteryStatusBox = (LinearLayout) view.findViewById(R.id.device_battery_status_box);
batteryStatusLabel = (TextView) view.findViewById(R.id.battery_status);
batteryIcon = (ImageView) view.findViewById(R.id.device_battery_status);
fetchActivityDataBox = (LinearLayout) view.findViewById(R.id.device_action_fetch_activity_box);
fetchActivityData = (ImageView) view.findViewById(R.id.device_action_fetch_activity);
busyIndicator = (ProgressBar) view.findViewById(R.id.device_busy_indicator);
takeScreenshotView = (ImageView) view.findViewById(R.id.device_action_take_screenshot);
manageAppsView = (ImageView) view.findViewById(R.id.device_action_manage_apps);
setAlarmsView = (ImageView) view.findViewById(R.id.device_action_set_alarms);
showActivityGraphs = (ImageView) view.findViewById(R.id.device_action_show_activity_graphs);
showAudioSettings = (ImageView) view.findViewById(R.id.device_action_show_audio_settings);
deviceInfoView = (ImageView) view.findViewById(R.id.device_info_image);
deviceInfoBox = (RelativeLayout) view.findViewById(R.id.device_item_infos_box);
//overflow
deviceInfoList = (ListView) view.findViewById(R.id.device_item_infos);
findDevice = (ImageView) view.findViewById(R.id.device_action_find);
removeDevice = (ImageView) view.findViewById(R.id.device_action_remove);
}
}
public void justifyListViewHeightBasedOnChildren(ListView listView) {
ArrayAdapter adapter = (ArrayAdapter) listView.getAdapter();
if (adapter == null) {
return;
}
ViewGroup vg = listView;
int totalHeight = 0;
for (int i = 0; i < adapter.getCount(); i++) {
View listItem = adapter.getView(i, null, vg);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams par = listView.getLayoutParams();
par.height = totalHeight + (listView.getDividerHeight() * (adapter.getCount() - 1));
listView.setLayoutParams(par);
listView.requestLayout();
}
private String getUniqueDeviceName(GBDevice device) {
String deviceName = device.getName();
if (!isUniqueDeviceName(device, deviceName)) {
if (device.getModel() != null) {
deviceName = deviceName + " " + device.getModel();
if (!isUniqueDeviceName(device, deviceName)) {
deviceName = deviceName + " " + device.getShortAddress();
}
} else {
deviceName = deviceName + " " + device.getShortAddress();
}
}
return deviceName;
}
private boolean isUniqueDeviceName(GBDevice device, String deviceName) {
for (int i = 0; i < deviceList.size(); i++) {
GBDevice item = deviceList.get(i);
if (item == device) {
continue;
}
if (deviceName.equals(item.getName())) {
return false;
}
}
return true;
}
private void showTransientSnackbar(int resource) {
Snackbar snackbar = Snackbar.make(parent, resource, Snackbar.LENGTH_SHORT);
View snackbarView = snackbar.getView();
// change snackbar text color
int snackbarTextId = android.support.design.R.id.snackbar_text;
TextView textView = (TextView) snackbarView.findViewById(snackbarTextId);
//textView.setTextColor();
//snackbarView.setBackgroundColor(Color.MAGENTA);
snackbar.show();
}
}

View File

@ -1,55 +1,145 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
public class GBDeviceAppAdapter extends ArrayAdapter<GBDeviceApp> {
/**
* Adapter for displaying GBDeviceApp instances.
*/
private final Context context;
public class GBDeviceAppAdapter extends RecyclerView.Adapter<GBDeviceAppAdapter.AppViewHolder> {
public GBDeviceAppAdapter(Context context, List<GBDeviceApp> appList) {
super(context, 0, appList);
private final int mLayoutId;
private final List<GBDeviceApp> appList;
private final AbstractAppManagerFragment mParentFragment;
this.context = context;
public List<GBDeviceApp> getAppList() {
return appList;
}
public GBDeviceAppAdapter(List<GBDeviceApp> list, int layoutId, AbstractAppManagerFragment parentFragment) {
mLayoutId = layoutId;
appList = list;
mParentFragment = parentFragment;
}
@Override
public View getView(int position, View view, ViewGroup parent) {
GBDeviceApp deviceApp = getItem(position);
public long getItemId(int position) {
return appList.get(position).getUUID().getLeastSignificantBits();
}
if (view == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@Override
public int getItemCount() {
return appList.size();
}
view = inflater.inflate(R.layout.item_with_details, parent, false);
}
TextView deviceAppVersionAuthorLabel = (TextView) view.findViewById(R.id.item_details);
TextView deviceAppNameLabel = (TextView) view.findViewById(R.id.item_name);
ImageView deviceImageView = (ImageView) view.findViewById(R.id.item_image);
@Override
public GBDeviceAppAdapter.AppViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutId, parent, false);
return new AppViewHolder(view);
}
@Override
public void onBindViewHolder(final AppViewHolder holder, int position) {
final GBDeviceApp deviceApp = appList.get(position);
holder.mDeviceAppVersionAuthorLabel.setText(GBApplication.getContext().getString(R.string.appversion_by_creator, deviceApp.getVersion(), deviceApp.getCreator()));
// FIXME: replace with small icons
String appNameLabelText = deviceApp.getName();
holder.mDeviceAppNameLabel.setText(appNameLabelText);
deviceAppVersionAuthorLabel.setText(getContext().getString(R.string.appversion_by_creator, deviceApp.getVersion(), deviceApp.getCreator()));
deviceAppNameLabel.setText(deviceApp.getName());
switch (deviceApp.getType()) {
case APP_GENERIC:
holder.mDeviceImageView.setImageResource(R.drawable.ic_watchapp);
break;
case APP_ACTIVITYTRACKER:
deviceImageView.setImageResource(R.drawable.ic_activitytracker);
holder.mDeviceImageView.setImageResource(R.drawable.ic_activitytracker);
break;
case APP_SYSTEM:
holder.mDeviceImageView.setImageResource(R.drawable.ic_systemapp);
break;
case WATCHFACE:
deviceImageView.setImageResource(R.drawable.ic_watchface);
holder.mDeviceImageView.setImageResource(R.drawable.ic_watchface);
break;
default:
deviceImageView.setImageResource(R.drawable.ic_device_pebble);
holder.mDeviceImageView.setImageResource(R.drawable.ic_watchapp);
}
return view;
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
UUID uuid = deviceApp.getUUID();
GBApplication.deviceService().onAppStart(uuid, true);
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
return mParentFragment.openPopupMenu(view, deviceApp);
}
});
holder.mDragHandle.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
mParentFragment.startDragging(holder);
return true;
}
});
}
public void onItemMove(int from, int to) {
Collections.swap(appList, from, to);
notifyItemMoved(from, to);
}
public class AppViewHolder extends RecyclerView.ViewHolder {
final TextView mDeviceAppVersionAuthorLabel;
final TextView mDeviceAppNameLabel;
final ImageView mDeviceImageView;
final ImageView mDragHandle;
AppViewHolder(View itemView) {
super(itemView);
mDeviceAppVersionAuthorLabel = (TextView) itemView.findViewById(R.id.item_details);
mDeviceAppNameLabel = (TextView) itemView.findViewById(R.id.item_name);
mDeviceImageView = (ImageView) itemView.findViewById(R.id.item_image);
mDragHandle = (ImageView) itemView.findViewById(R.id.drag_handle);
}
}
}

View File

@ -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/>. */
package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.content.Context;
@ -13,9 +29,17 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
/**
* Adapter for displaying generic ItemWithDetails instances.
*/
public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
public static final int SIZE_SMALL = 1;
public static final int SIZE_MEDIUM = 2;
public static final int SIZE_LARGE = 3;
private final Context context;
private boolean horizontalAlignment;
private int size = SIZE_MEDIUM;
public ItemWithDetailsAdapter(Context context, List<ItemWithDetails> items) {
super(context, 0, items);
@ -23,6 +47,10 @@ public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
this.context = context;
}
public void setHorizontalAlignment(boolean horizontalAlignment) {
this.horizontalAlignment = horizontalAlignment;
}
@Override
public View getView(int position, View view, ViewGroup parent) {
ItemWithDetails item = getItem(position);
@ -31,7 +59,18 @@ public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.item_with_details, parent, false);
if (horizontalAlignment) {
view = inflater.inflate(R.layout.item_with_details_horizontal, parent, false);
} else {
switch (size) {
case SIZE_SMALL:
view = inflater.inflate(R.layout.item_with_details_small, parent, false);
break;
default:
view = inflater.inflate(R.layout.item_with_details, parent, false);
break;
}
}
}
ImageView iconView = (ImageView) view.findViewById(R.id.item_image);
TextView nameView = (TextView) view.findViewById(R.id.item_name);
@ -43,4 +82,12 @@ public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
return view;
}
public void setSize(int size) {
this.size = size;
}
public int getSize() {
return size;
}
}

View File

@ -0,0 +1,114 @@
/* 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.contentprovider;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.content.LocalBroadcastManager;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class PebbleContentProvider extends ContentProvider {
public static final int COLUMN_CONNECTED = 0;
public static final int COLUMN_APPMSG_SUPPORT = 1;
public static final int COLUMN_DATALOGGING_SUPPORT = 2;
public static final int COLUMN_VERSION_MAJOR = 3;
public static final int COLUMN_VERSION_MINOR = 4;
public static final int COLUMN_VERSION_POINT = 5;
public static final int COLUMN_VERSION_TAG = 6;
// this is only needed for the MatrixCursor constructor
public static final String[] columnNames = new String[]{"0", "1", "2", "3", "4", "5", "6"};
static final String PROVIDER_NAME = "com.getpebble.android.provider";
static final String URL = "content://" + PROVIDER_NAME + "/state";
static final Uri CONTENT_URI = Uri.parse(URL);
private GBDevice mGBDevice = null;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(GBDevice.ACTION_DEVICE_CHANGED)) {
mGBDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
}
}
};
@Override
public boolean onCreate() {
LocalBroadcastManager.getInstance(this.getContext()).registerReceiver(mReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
return true;
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
if (uri.equals(CONTENT_URI)) {
MatrixCursor mc = new MatrixCursor(columnNames);
int connected = 0;
int pebbleKit = 0;
Prefs prefs = GBApplication.getPrefs();
if (prefs.getBoolean("pebble_enable_pebblekit", false)) {
pebbleKit = 1;
}
String fwString = "unknown";
if (mGBDevice != null && mGBDevice.getType() == DeviceType.PEBBLE && mGBDevice.isInitialized()) {
connected = 1;
fwString = mGBDevice.getFirmwareVersion();
}
mc.addRow(new Object[]{connected, pebbleKit, pebbleKit, 3, 8, 2, fwString});
return mc;
} else {
return null;
}
}
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}

View File

@ -1,220 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.schema.ActivityDBCreationScript;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.DATABASE_NAME;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_INTENSITY;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_PROVIDER;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TIMESTAMP;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TYPE;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES;
public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandler {
private static final Logger LOG = LoggerFactory.getLogger(ActivityDatabaseHandler.class);
private static final int DATABASE_VERSION = 5;
public ActivityDatabaseHandler(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
try {
ActivityDBCreationScript script = new ActivityDBCreationScript();
script.createSchema(db);
} catch (RuntimeException ex) {
GB.toast("Error creating database.", Toast.LENGTH_SHORT, GB.ERROR, ex);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
try {
for (int i = oldVersion + 1; i <= newVersion; i++) {
DBUpdateScript updater = getUpdateScript(db, i);
if (updater != null) {
LOG.info("upgrading activity database to version " + i);
updater.upgradeSchema(db);
}
}
LOG.info("activity database is now at version " + newVersion);
} catch (RuntimeException ex) {
GB.toast("Error upgrading database.", Toast.LENGTH_SHORT, GB.ERROR, ex);
throw ex; // reject upgrade
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
try {
for (int i = oldVersion; i >= newVersion; i--) {
DBUpdateScript updater = getUpdateScript(db, i);
if (updater != null) {
LOG.info("downgrading activity database to version " + (i - 1));
updater.downgradeSchema(db);
}
}
LOG.info("activity database is now at version " + newVersion);
} catch (RuntimeException ex) {
GB.toast("Error downgrading database.", Toast.LENGTH_SHORT, GB.ERROR, ex);
throw ex; // reject downgrade
}
}
private DBUpdateScript getUpdateScript(SQLiteDatabase db, int version) {
try {
Class<?> updateClass = getClass().getClassLoader().loadClass(getClass().getPackage().getName() + ".schema.ActivityDBUpdate_" + version);
return (DBUpdateScript) updateClass.newInstance();
} catch (ClassNotFoundException e) {
return null;
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("Error instantiating DBUpdate class for version " + version, e);
}
}
public void addGBActivitySample(ActivitySample sample) {
try (SQLiteDatabase db = this.getWritableDatabase()) {
ContentValues values = new ContentValues();
values.put(KEY_TIMESTAMP, sample.getTimestamp());
values.put(KEY_PROVIDER, sample.getProvider().getID());
values.put(KEY_INTENSITY, sample.getRawIntensity());
values.put(KEY_STEPS, sample.getSteps());
values.put(KEY_TYPE, sample.getRawKind());
db.insert(TABLE_GBACTIVITYSAMPLES, null, values);
}
}
/**
* Adds the a new sample to the database
*
* @param timestamp the timestamp of the same, second-based!
* @param provider the SampleProvider ID
* @param intensity the sample's raw intensity value
* @param steps the sample's steps value
* @param kind the raw activity kind of the sample
*/
@Override
public void addGBActivitySample(int timestamp, byte provider, short intensity, short steps, byte kind) {
if (intensity < 0) {
LOG.error("negative intensity received, ignoring");
intensity = 0;
}
if (steps < 0) {
LOG.error("negative steps received, ignoring");
steps = 0;
}
try (SQLiteDatabase db = this.getWritableDatabase()) {
ContentValues values = new ContentValues();
values.put(KEY_TIMESTAMP, timestamp);
values.put(KEY_PROVIDER, provider);
values.put(KEY_INTENSITY, intensity);
values.put(KEY_STEPS, steps);
values.put(KEY_TYPE, kind);
db.insert(TABLE_GBACTIVITYSAMPLES, null, values);
}
}
public ArrayList<ActivitySample> getSleepSamples(int timestamp_from, int timestamp_to, SampleProvider provider) {
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_SLEEP, provider);
}
public ArrayList<ActivitySample> getActivitySamples(int timestamp_from, int timestamp_to, SampleProvider provider) {
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ACTIVITY, provider);
}
@Override
public SQLiteOpenHelper getHelper() {
return this;
}
@Override
public void release() {
GBApplication.releaseDB();
}
public ArrayList<ActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to, SampleProvider provider) {
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL, provider);
}
/**
* Returns all available activity samples from between the two timestamps (inclusive), of the given
* provided and type(s).
*
* @param timestamp_from
* @param timestamp_to
* @param activityTypes ORed combination of #TYPE_DEEP_SLEEP, #TYPE_LIGHT_SLEEP, #TYPE_ACTIVITY
* @param provider the producer of the samples to be sought
* @return
*/
private ArrayList<ActivitySample> getGBActivitySamples(int timestamp_from, int timestamp_to, int activityTypes, SampleProvider provider) {
if (timestamp_to < 0) {
throw new IllegalArgumentException("negative timestamp_to");
}
if (timestamp_from < 0) {
throw new IllegalArgumentException("negative timestamp_from");
}
ArrayList<ActivitySample> samples = new ArrayList<ActivitySample>();
final String where = "(provider=" + provider.getID() + " and timestamp>=" + timestamp_from + " and timestamp<=" + timestamp_to + getWhereClauseFor(activityTypes, provider) + ")";
LOG.info("Activity query where: " + where);
final String order = "timestamp";
try (SQLiteDatabase db = this.getReadableDatabase()) {
try (Cursor cursor = db.query(TABLE_GBACTIVITYSAMPLES, null, where, null, null, null, order)) {
LOG.info("Activity query result: " + cursor.getCount() + " samples");
if (cursor.moveToFirst()) {
do {
GBActivitySample sample = new GBActivitySample(
provider,
cursor.getInt(cursor.getColumnIndex(KEY_TIMESTAMP)),
cursor.getShort(cursor.getColumnIndex(KEY_INTENSITY)),
cursor.getShort(cursor.getColumnIndex(KEY_STEPS)),
(byte) cursor.getShort(cursor.getColumnIndex(KEY_TYPE)));
samples.add(sample);
} while (cursor.moveToNext());
}
}
}
return samples;
}
private String getWhereClauseFor(int activityTypes, SampleProvider provider) {
if (activityTypes == ActivityKind.TYPE_ALL) {
return ""; // no further restriction
}
StringBuilder builder = new StringBuilder(" and (");
byte[] dbActivityTypes = ActivityKind.mapToDBActivityTypes(activityTypes, provider);
for (int i = 0; i < dbActivityTypes.length; i++) {
builder.append(" type=").append(dbActivityTypes[i]);
if (i + 1 < dbActivityTypes.length) {
builder.append(" or ");
}
}
builder.append(')');
return builder.toString();
}
}

View File

@ -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/>. */
package nodomain.freeyourgadget.gadgetbridge.database;
import android.content.Context;
@ -26,16 +42,10 @@ public abstract class DBAccess extends AsyncTask {
@Override
protected Object doInBackground(Object[] params) {
DBHandler handler = null;
try {
handler = GBApplication.acquireDB();
doInBackground(handler);
try (DBHandler db = GBApplication.acquireDB()) {
doInBackground(db);
} catch (Exception e) {
mError = e;
} finally {
if (handler != null) {
handler.release();
}
}
return null;
}

View File

@ -1,14 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.database;
public class DBConstants {
public static final String DATABASE_NAME = "ActivityDatabase";
public static final String TABLE_GBACTIVITYSAMPLES = "GBActivitySamples";
public static final String TABLE_STEPS_PER_DAY = "StepsPerDay";
public static final String KEY_TIMESTAMP = "timestamp";
public static final String KEY_PROVIDER = "provider";
public static final String KEY_INTENSITY = "intensity";
public static final String KEY_STEPS = "steps";
public static final String KEY_TYPE = "type";
}

View File

@ -1,30 +1,53 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, JohnnySun
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.database;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
public interface DBHandler {
public SQLiteOpenHelper getHelper();
/**
* Provides lowlevel access to the database.
*/
public interface DBHandler extends AutoCloseable {
/**
* Closes the database.
*/
void closeDb();
/**
* Releases the DB handler. No access may be performed after calling this method.
* Same as calling {@link GBApplication#releaseDB()}
* Opens the database. Note that this is only possible after an explicit
* #closeDb(). Initially the db is implicitly open.
*/
void release();
void openDb();
List<ActivitySample> getAllActivitySamples(int tsFrom, int tsTo, SampleProvider provider);
SQLiteOpenHelper getHelper();
List<ActivitySample> getActivitySamples(int tsFrom, int tsTo, SampleProvider provider);
/**
* Releases the DB handler. No DB access will be possible before
* #openDb() will be called.
*/
void close() throws Exception;
List<ActivitySample> getSleepSamples(int tsFrom, int tsTo, SampleProvider provider);
SQLiteDatabase getDatabase();
void addGBActivitySample(int timestamp, byte provider, short intensity, short steps, byte kind);
SQLiteDatabase getWritableDatabase();
DaoMaster getDaoMaster();
DaoSession getDaoSession();
}

View File

@ -1,60 +1,135 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, JohnnySun
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.database;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import de.greenrobot.dao.Property;
import de.greenrobot.dao.query.Query;
import de.greenrobot.dao.query.QueryBuilder;
import de.greenrobot.dao.query.WhereCondition;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.entities.ActivityDescription;
import nodomain.freeyourgadget.gadgetbridge.entities.ActivityDescriptionDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributes;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceDao;
import nodomain.freeyourgadget.gadgetbridge.entities.Tag;
import nodomain.freeyourgadget.gadgetbridge.entities.TagDao;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.entities.UserAttributes;
import nodomain.freeyourgadget.gadgetbridge.entities.UserDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.ValidByDate;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
/**
* Provides utiliy access to some common entities, so you won't need to use
* their DAO classes.
* <p/>
* Maybe this code should actually be in the DAO classes themselves, but then
* these should be under revision control instead of 100% generated at build time.
*/
public class DBHelper {
private static final Logger LOG = LoggerFactory.getLogger(DBHelper.class);
private final Context context;
public DBHelper(Context context) {
this.context = context;
}
private String getClosedDBPath(SQLiteOpenHelper dbHandler) throws IllegalStateException {
SQLiteDatabase db = dbHandler.getReadableDatabase();
/**
* Closes the database and returns its name.
* Important: after calling this, you have to DBHandler#openDb() it again
* to get it back to work.
*
* @param dbHandler
* @return
* @throws IllegalStateException
*/
private String getClosedDBPath(DBHandler dbHandler) throws IllegalStateException {
SQLiteDatabase db = dbHandler.getDatabase();
String path = db.getPath();
db.close();
dbHandler.closeDb();
if (db.isOpen()) { // reference counted, so may still be open
throw new IllegalStateException("Database must be closed");
}
return path;
}
public File exportDB(SQLiteOpenHelper dbHandler, File toDir) throws IllegalStateException, IOException {
public File exportDB(DBHandler dbHandler, File toDir) throws IllegalStateException, IOException {
String dbPath = getClosedDBPath(dbHandler);
File sourceFile = new File(dbPath);
File destFile = new File(toDir, sourceFile.getName());
if (destFile.exists()) {
File backup = new File(toDir, destFile.getName() + "_" + getDate());
destFile.renameTo(backup);
} else if (!toDir.exists()) {
if (!toDir.mkdirs()) {
throw new IOException("Unable to create directory: " + toDir.getAbsolutePath());
try {
File sourceFile = new File(dbPath);
File destFile = new File(toDir, sourceFile.getName());
if (destFile.exists()) {
File backup = new File(toDir, destFile.getName() + "_" + getDate());
destFile.renameTo(backup);
} else if (!toDir.exists()) {
if (!toDir.mkdirs()) {
throw new IOException("Unable to create directory: " + toDir.getAbsolutePath());
}
}
}
FileUtils.copyFile(sourceFile, destFile);
return destFile;
FileUtils.copyFile(sourceFile, destFile);
return destFile;
} finally {
dbHandler.openDb();
}
}
private String getDate() {
return new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US).format(new Date());
}
public void importDB(SQLiteOpenHelper dbHandler, File fromFile) throws IllegalStateException, IOException {
public void importDB(DBHandler dbHandler, File fromFile) throws IllegalStateException, IOException {
String dbPath = getClosedDBPath(dbHandler);
File toFile = new File(dbPath);
FileUtils.copyFile(fromFile, toFile);
try {
File toFile = new File(dbPath);
FileUtils.copyFile(fromFile, toFile);
} finally {
dbHandler.openDb();
}
}
public void validateDB(SQLiteOpenHelper dbHandler) throws IOException {
@ -70,16 +145,416 @@ public class DBHelper {
db.execSQL(statement);
}
public boolean existsDB(String dbName) {
File path = context.getDatabasePath(dbName);
return path != null && path.exists();
}
public static boolean existsColumn(String tableName, String columnName, SQLiteDatabase db) {
try (Cursor res = db.rawQuery("PRAGMA table_info('" + tableName + "')", null)) {
int index = res.getColumnIndex("name");
if (index < 1) {
return false; // something's really wrong
}
while (res.moveToNext()) {
String cn = res.getString(index);
if (columnName.equals(cn)) {
return true;
}
}
}
return false;
}
/**
* WITHOUT ROWID is only available with sqlite 3.8.2, which is available
* with Lollipop and later.
*
* @return the "WITHOUT ROWID" string or an empty string for pre-Lollipop devices
*/
@NonNull
public static String getWithoutRowId() {
if (GBApplication.isRunningLollipopOrLater()) {
return " WITHOUT ROWID;";
}
return "";
}
/**
* Looks up the user entity in the database. If a user exists already, it will
* be updated with the current preferences values. If no user exists yet, it will
* be created in the database.
*
* Note: so far there is only ever a single user; there is no multi-user support yet
* @param session
* @return the User entity
*/
@NonNull
public static User getUser(DaoSession session) {
ActivityUser prefsUser = new ActivityUser();
UserDao userDao = session.getUserDao();
User user;
List<User> users = userDao.loadAll();
if (users.isEmpty()) {
user = createUser(prefsUser, session);
} else {
user = users.get(0); // TODO: multiple users support?
ensureUserUpToDate(user, prefsUser, session);
}
ensureUserAttributes(user, prefsUser, session);
return user;
}
@NonNull
public static UserAttributes getUserAttributes(User user) {
List<UserAttributes> list = user.getUserAttributesList();
if (list.isEmpty()) {
throw new IllegalStateException("user has no attributes");
}
return list.get(0);
}
@NonNull
private static User createUser(ActivityUser prefsUser, DaoSession session) {
User user = new User();
ensureUserUpToDate(user, prefsUser, session);
return user;
}
private static void ensureUserUpToDate(User user, ActivityUser prefsUser, DaoSession session) {
if (!isUserUpToDate(user, prefsUser)) {
user.setName(prefsUser.getName());
user.setBirthday(prefsUser.getUserBirthday());
user.setGender(prefsUser.getGender());
if (user.getId() == null) {
session.getUserDao().insert(user);
} else {
session.getUserDao().update(user);
}
}
}
public static boolean isUserUpToDate(User user, ActivityUser prefsUser) {
if (!Objects.equals(user.getName(), prefsUser.getName())) {
return false;
}
if (!Objects.equals(user.getBirthday(), prefsUser.getUserBirthday())) {
return false;
}
if (user.getGender() != prefsUser.getGender()) {
return false;
}
return true;
}
private static void ensureUserAttributes(User user, ActivityUser prefsUser, DaoSession session) {
List<UserAttributes> userAttributes = user.getUserAttributesList();
UserAttributes[] previousUserAttributes = new UserAttributes[1];
if (hasUpToDateUserAttributes(userAttributes, prefsUser, previousUserAttributes)) {
return;
}
Calendar now = DateTimeUtils.getCalendarUTC();
invalidateUserAttributes(previousUserAttributes[0], now, session);
UserAttributes attributes = new UserAttributes();
attributes.setValidFromUTC(now.getTime());
attributes.setHeightCM(prefsUser.getHeightCm());
attributes.setWeightKG(prefsUser.getWeightKg());
attributes.setSleepGoalHPD(prefsUser.getSleepDuration());
attributes.setStepsGoalSPD(prefsUser.getStepsGoal());
attributes.setUserId(user.getId());
session.getUserAttributesDao().insert(attributes);
// sort order is important, so we re-fetch from the db
// userAttributes.add(attributes);
user.resetUserAttributesList();
}
private static void invalidateUserAttributes(UserAttributes userAttributes, Calendar now, DaoSession session) {
if (userAttributes != null) {
Calendar invalid = (Calendar) now.clone();
invalid.add(Calendar.MINUTE, -1);
userAttributes.setValidToUTC(invalid.getTime());
session.getUserAttributesDao().update(userAttributes);
}
}
private static boolean hasUpToDateUserAttributes(List<UserAttributes> userAttributes, ActivityUser prefsUser, UserAttributes[] outPreviousUserAttributes) {
for (UserAttributes attr : userAttributes) {
if (!isValidNow(attr)) {
continue;
}
if (isEqual(attr, prefsUser)) {
return true;
} else {
outPreviousUserAttributes[0] = attr;
}
}
return false;
}
// TODO: move this into db queries?
private static boolean isValidNow(ValidByDate element) {
Calendar cal = DateTimeUtils.getCalendarUTC();
Date nowUTC = cal.getTime();
return isValid(element, nowUTC);
}
private static boolean isValid(ValidByDate element, Date nowUTC) {
Date validFromUTC = element.getValidFromUTC();
Date validToUTC = element.getValidToUTC();
if (nowUTC.before(validFromUTC)) {
return false;
}
if (validToUTC != null && nowUTC.after(validToUTC)) {
return false;
}
return true;
}
private static boolean isEqual(UserAttributes attr, ActivityUser prefsUser) {
if (prefsUser.getHeightCm() != attr.getHeightCM()) {
LOG.info("user height changed to " + prefsUser.getHeightCm() + " from " + attr.getHeightCM());
return false;
}
if (prefsUser.getWeightKg() != attr.getWeightKG()) {
LOG.info("user changed to " + prefsUser.getWeightKg() + " from " + attr.getWeightKG());
return false;
}
if (!Integer.valueOf(prefsUser.getSleepDuration()).equals(attr.getSleepGoalHPD())) {
LOG.info("user sleep goal changed to " + prefsUser.getSleepDuration() + " from " + attr.getSleepGoalHPD());
return false;
}
if (!Integer.valueOf(prefsUser.getStepsGoal()).equals(attr.getStepsGoalSPD())) {
LOG.info("user steps goal changed to " + prefsUser.getStepsGoal() + " from " + attr.getStepsGoalSPD());
return false;
}
return true;
}
private static boolean isEqual(DeviceAttributes attr, GBDevice gbDevice) {
if (!Objects.equals(attr.getFirmwareVersion1(), gbDevice.getFirmwareVersion())) {
return false;
}
if (!Objects.equals(attr.getFirmwareVersion2(), gbDevice.getFirmwareVersion2())) {
return false;
}
if (!Objects.equals(attr.getVolatileIdentifier(), gbDevice.getVolatileAddress())) {
return false;
}
return true;
}
public static Device findDevice(GBDevice gbDevice, DaoSession session) {
DeviceDao deviceDao = session.getDeviceDao();
Query<Device> query = deviceDao.queryBuilder().where(DeviceDao.Properties.Identifier.eq(gbDevice.getAddress())).build();
List<Device> devices = query.list();
if (devices.size() > 0) {
return devices.get(0);
}
return null;
}
/**
* Returns all active (that is, not old, archived ones) from the database.
* (currently the active handling is not available)
* @param daoSession
*/
public static List<Device> getActiveDevices(DaoSession daoSession) {
return daoSession.getDeviceDao().loadAll();
}
/**
* Looks up in the database the Device entity corresponding to the GBDevice. If a device
* exists already, it will be updated with the current preferences values. If no device exists
* yet, it will be created in the database.
*
* @param session
* @return the device entity corresponding to the given GBDevice
*/
public static Device getDevice(GBDevice gbDevice, DaoSession session) {
Device device = findDevice(gbDevice, session);
if (device == null) {
device = createDevice(gbDevice, session);
} else {
ensureDeviceUpToDate(device, gbDevice, session);
}
if (gbDevice.isInitialized()) {
ensureDeviceAttributes(device, gbDevice, session);
}
return device;
}
@NonNull
public static DeviceAttributes getDeviceAttributes(Device device) {
List<DeviceAttributes> list = device.getDeviceAttributesList();
if (list.isEmpty()) {
throw new IllegalStateException("device has no attributes");
}
return list.get(0);
}
private static void ensureDeviceUpToDate(Device device, GBDevice gbDevice, DaoSession session) {
if (!isDeviceUpToDate(device, gbDevice)) {
device.setIdentifier(gbDevice.getAddress());
device.setName(gbDevice.getName());
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
device.setManufacturer(coordinator.getManufacturer());
device.setType(gbDevice.getType().getKey());
device.setModel(gbDevice.getModel());
if (device.getId() == null) {
session.getDeviceDao().insert(device);
} else {
session.getDeviceDao().update(device);
}
}
}
private static boolean isDeviceUpToDate(Device device, GBDevice gbDevice) {
if (!Objects.equals(device.getIdentifier(), gbDevice.getAddress())) {
return false;
}
if (!Objects.equals(device.getName(), gbDevice.getName())) {
return false;
}
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
if (!Objects.equals(device.getManufacturer(), coordinator.getManufacturer())) {
return false;
}
if (device.getType() != gbDevice.getType().getKey()) {
return false;
}
if (!Objects.equals(device.getModel(), gbDevice.getModel())) {
return false;
}
return true;
}
private static Device createDevice(GBDevice gbDevice, DaoSession session) {
Device device = new Device();
ensureDeviceUpToDate(device, gbDevice, session);
return device;
}
private static void ensureDeviceAttributes(Device device, GBDevice gbDevice, DaoSession session) {
List<DeviceAttributes> deviceAttributes = device.getDeviceAttributesList();
DeviceAttributes[] previousDeviceAttributes = new DeviceAttributes[1];
if (hasUpToDateDeviceAttributes(deviceAttributes, gbDevice, previousDeviceAttributes)) {
return;
}
Calendar now = DateTimeUtils.getCalendarUTC();
invalidateDeviceAttributes(previousDeviceAttributes[0], now, session);
DeviceAttributes attributes = new DeviceAttributes();
attributes.setDeviceId(device.getId());
attributes.setValidFromUTC(now.getTime());
attributes.setFirmwareVersion1(gbDevice.getFirmwareVersion());
attributes.setFirmwareVersion2(gbDevice.getFirmwareVersion2());
attributes.setVolatileIdentifier(gbDevice.getVolatileAddress());
DeviceAttributesDao attributesDao = session.getDeviceAttributesDao();
attributesDao.insert(attributes);
// sort order is important, so we re-fetch from the db
// deviceAttributes.add(attributes);
device.resetDeviceAttributesList();
}
private static void invalidateDeviceAttributes(DeviceAttributes deviceAttributes, Calendar now, DaoSession session) {
if (deviceAttributes != null) {
Calendar invalid = (Calendar) now.clone();
invalid.add(Calendar.MINUTE, -1);
deviceAttributes.setValidToUTC(invalid.getTime());
session.getDeviceAttributesDao().update(deviceAttributes);
}
}
private static boolean hasUpToDateDeviceAttributes(List<DeviceAttributes> deviceAttributes, GBDevice gbDevice, DeviceAttributes[] outPreviousAttributes) {
for (DeviceAttributes attr : deviceAttributes) {
if (!isValidNow(attr)) {
continue;
}
if (isEqual(attr, gbDevice)) {
return true;
} else {
outPreviousAttributes[0] = attr;
}
}
return false;
}
@NonNull
public static List<ActivityDescription> findActivityDecriptions(@NonNull User user, int tsFrom, int tsTo, @NonNull DaoSession session) {
Property tsFromProperty = ActivityDescriptionDao.Properties.TimestampFrom;
Property tsToProperty = ActivityDescriptionDao.Properties.TimestampTo;
Property userIdProperty = ActivityDescriptionDao.Properties.UserId;
QueryBuilder<ActivityDescription> qb = session.getActivityDescriptionDao().queryBuilder();
qb.where(userIdProperty.eq(user.getId()), isAtLeastPartiallyInRange(qb, tsFromProperty, tsToProperty, tsFrom, tsTo));
List<ActivityDescription> descriptions = qb.build().list();
return descriptions;
}
/**
* Returns a condition that matches when the range of the entity (tsFromProperty..tsToProperty)
* is completely or partially inside the range tsFrom..tsTo.
* @param qb the query builder to use
* @param tsFromProperty the property indicating the start of the entity's range
* @param tsToProperty the property indicating the end of the entity's range
* @param tsFrom the timestamp indicating the start of the range to match
* @param tsTo the timestamp indicating the end of the range to match
* @param <T> the query builder's type parameter
* @return the range WhereCondition
*/
private static <T> WhereCondition isAtLeastPartiallyInRange(QueryBuilder<T> qb, Property tsFromProperty, Property tsToProperty, int tsFrom, int tsTo) {
return qb.and(tsFromProperty.lt(tsTo), tsToProperty.gt(tsFrom));
}
@NonNull
public static ActivityDescription createActivityDescription(@NonNull User user, int tsFrom, int tsTo, @NonNull DaoSession session) {
ActivityDescription desc = new ActivityDescription();
desc.setUser(user);
desc.setTimestampFrom(tsFrom);
desc.setTimestampTo(tsTo);
session.getActivityDescriptionDao().insertOrReplace(desc);
return desc;
}
@NonNull
public static Tag getTag(@NonNull User user, @NonNull String name, @NonNull DaoSession session) {
TagDao tagDao = session.getTagDao();
QueryBuilder<Tag> qb = tagDao.queryBuilder();
Query<Tag> query = qb.where(TagDao.Properties.UserId.eq(user.getId()), TagDao.Properties.Name.eq(name)).build();
List<Tag> tags = query.list();
if (tags.size() > 0) {
return tags.get(0);
}
return createTag(user, name, null, session);
}
static Tag createTag(@NonNull User user, @NonNull String name, @Nullable String description, @NonNull DaoSession session) {
Tag tag = new Tag();
tag.setUserId(user.getId());
tag.setName(name);
tag.setDescription(description);
session.getTagDao().insertOrReplace(tag);
return tag;
}
public static void clearSession() {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
DaoSession session = dbHandler.getDaoSession();
session.clear();
} catch (Exception e) {
LOG.warn("Unable to acquire database to clear the session", e);
}
}
}

View File

@ -0,0 +1,50 @@
/* 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.database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.schema.SchemaMigration;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
public class DBOpenHelper extends DaoMaster.OpenHelper {
private final String updaterClassNamePrefix;
private final Context context;
public DBOpenHelper(Context context, String dbName, SQLiteDatabase.CursorFactory factory) {
super(context, dbName, factory);
updaterClassNamePrefix = dbName + "Update_";
this.context = context;
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
DaoMaster.createAllTables(db, true);
new SchemaMigration(updaterClassNamePrefix).onUpgrade(db, oldVersion, newVersion);
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
DaoMaster.createAllTables(db, true);
new SchemaMigration(updaterClassNamePrefix).onDowngrade(db, oldVersion, newVersion);
}
public Context getContext() {
return context;
}
}

View File

@ -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/>. */
package nodomain.freeyourgadget.gadgetbridge.database;
import android.database.sqlite.SQLiteDatabase;

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2015-2017 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.database;
/**

View File

@ -1,25 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.database.schema;
import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_INTENSITY;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_PROVIDER;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TIMESTAMP;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TYPE;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES;
public class ActivityDBCreationScript {
public void createSchema(SQLiteDatabase db) {
String CREATE_GBACTIVITYSAMPLES_TABLE = "CREATE TABLE " + TABLE_GBACTIVITYSAMPLES + " ("
+ KEY_TIMESTAMP + " INT,"
+ KEY_PROVIDER + " TINYINT,"
+ KEY_INTENSITY + " SMALLINT,"
+ KEY_STEPS + " TINYINT,"
+ KEY_TYPE + " TINYINT,"
+ " PRIMARY KEY (" + KEY_TIMESTAMP + "," + KEY_PROVIDER + ") ON CONFLICT REPLACE)" + DBHelper.getWithoutRowId();
db.execSQL(CREATE_GBACTIVITYSAMPLES_TABLE);
}
}

View File

@ -1,31 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.database.schema;
import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES;
/**
* Upgrade and downgrade with DB versions <= 5 is not supported.
* Just recreates the default schema. Those GB versions may or may not
* work with that, but this code will probably not create a DB for them
* anyway.
*/
public class ActivityDBUpdate_4 extends ActivityDBCreationScript implements DBUpdateScript {
@Override
public void upgradeSchema(SQLiteDatabase db) {
recreateSchema(db);
}
@Override
public void downgradeSchema(SQLiteDatabase db) {
recreateSchema(db);
}
private void recreateSchema(SQLiteDatabase db) {
DBHelper.dropTable(TABLE_GBACTIVITYSAMPLES, db);
createSchema(db);
}
}

View File

@ -1,31 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.database.schema;
import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_PROVIDER;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TIMESTAMP;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_STEPS_PER_DAY;
/**
* Adds a table "STEPS_PER_DAY".
*/
public class ActivityDBUpdate_6 implements DBUpdateScript {
@Override
public void upgradeSchema(SQLiteDatabase db) {
String CREATE_STEPS_PER_DAY_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_STEPS_PER_DAY + " ("
+ KEY_TIMESTAMP + " INT,"
+ KEY_PROVIDER + " TINYINT,"
+ KEY_STEPS + " MEDIUMINT,"
+ " PRIMARY KEY (" + KEY_TIMESTAMP + "," + KEY_PROVIDER + ") ON CONFLICT REPLACE)" + DBHelper.getWithoutRowId();
db.execSQL(CREATE_STEPS_PER_DAY_TABLE);
}
@Override
public void downgradeSchema(SQLiteDatabase db) {
DBHelper.dropTable(TABLE_STEPS_PER_DAY, db);
}
}

View File

@ -0,0 +1,42 @@
/* Copyright (C) 2016-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.database.schema;
import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivitySampleDao;
/*
* adds heart rate column to health table
*/
public class GadgetbridgeUpdate_14 implements DBUpdateScript {
@Override
public void upgradeSchema(SQLiteDatabase db) {
if (!DBHelper.existsColumn(PebbleHealthActivitySampleDao.TABLENAME, PebbleHealthActivitySampleDao.Properties.HeartRate.columnName, db)) {
String ADD_COLUMN_HEART_RATE = "ALTER TABLE " + PebbleHealthActivitySampleDao.TABLENAME + " ADD COLUMN "
+ PebbleHealthActivitySampleDao.Properties.HeartRate.columnName + " INTEGER NOT NULL DEFAULT 0;";
db.execSQL(ADD_COLUMN_HEART_RATE);
}
}
@Override
public void downgradeSchema(SQLiteDatabase db) {
}
}

View File

@ -0,0 +1,42 @@
/* Copyright (C) 2016-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.database.schema;
import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
/*
* adds heart rate column to health table
*/
public class GadgetbridgeUpdate_15 implements DBUpdateScript {
@Override
public void upgradeSchema(SQLiteDatabase db) {
if (!DBHelper.existsColumn(DeviceAttributesDao.TABLENAME, DeviceAttributesDao.Properties.VolatileIdentifier.columnName, db)) {
String ADD_COLUMN_VOLATILE_IDENTIFIER = "ALTER TABLE " + DeviceAttributesDao.TABLENAME + " ADD COLUMN "
+ DeviceAttributesDao.Properties.VolatileIdentifier.columnName + " TEXT;";
db.execSQL(ADD_COLUMN_VOLATILE_IDENTIFIER);
}
}
@Override
public void downgradeSchema(SQLiteDatabase db) {
}
}

View File

@ -0,0 +1,38 @@
/* Copyright (C) 2017 protomors
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.database.schema;
import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
import nodomain.freeyourgadget.gadgetbridge.entities.No1F1ActivitySampleDao;
public class GadgetbridgeUpdate_17 implements DBUpdateScript {
@Override
public void upgradeSchema(SQLiteDatabase db) {
if (!DBHelper.existsColumn(No1F1ActivitySampleDao.TABLENAME, No1F1ActivitySampleDao.Properties.HeartRate.columnName, db)) {
String ADD_COLUMN_HEART_RATE = "ALTER TABLE " + No1F1ActivitySampleDao.TABLENAME + " ADD COLUMN "
+ No1F1ActivitySampleDao.Properties.HeartRate.columnName + " INTEGER NOT NULL DEFAULT 0;";
db.execSQL(ADD_COLUMN_HEART_RATE);
}
}
@Override
public void downgradeSchema(SQLiteDatabase db) {
}
}

View File

@ -0,0 +1,80 @@
/* 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.database.schema;
import android.database.sqlite.SQLiteDatabase;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class SchemaMigration {
private static final Logger LOG = LoggerFactory.getLogger(SchemaMigration.class);
private final String classNamePrefix;
public SchemaMigration(String updaterClassNamePrefix) {
classNamePrefix = updaterClassNamePrefix;
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
LOG.info("ActivityDatabase: schema upgrade requested from " + oldVersion + " to " + newVersion);
try {
for (int i = oldVersion + 1; i <= newVersion; i++) {
DBUpdateScript updater = getUpdateScript(db, i);
if (updater != null) {
LOG.info("upgrading activity database to version " + i);
updater.upgradeSchema(db);
}
}
LOG.info("activity database is now at version " + newVersion);
} catch (RuntimeException ex) {
GB.toast("Error upgrading database.", Toast.LENGTH_SHORT, GB.ERROR, ex);
throw ex; // reject upgrade
}
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
LOG.info("ActivityDatabase: schema downgrade requested from " + oldVersion + " to " + newVersion);
try {
for (int i = oldVersion; i >= newVersion; i--) {
DBUpdateScript updater = getUpdateScript(db, i);
if (updater != null) {
LOG.info("downgrading activity database to version " + (i - 1));
updater.downgradeSchema(db);
}
}
LOG.info("activity database is now at version " + newVersion);
} catch (RuntimeException ex) {
GB.toast("Error downgrading database.", Toast.LENGTH_SHORT, GB.ERROR, ex);
throw ex; // reject downgrade
}
}
private DBUpdateScript getUpdateScript(SQLiteDatabase db, int version) {
try {
Class<?> updateClass = getClass().getClassLoader().loadClass(getClass().getPackage().getName() + "." + classNamePrefix + version);
return (DBUpdateScript) updateClass.newInstance();
} catch (ClassNotFoundException e) {
return null;
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("Error instantiating DBUpdate class for version " + version, e);
}
}
}

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, 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.deviceevents;

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