mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-24 01:25:50 +01:00
Merge branch 'master' into y5_patch_test
# Conflicts: # GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java # app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/TeclastH30/TeclastH30Coordinator.java # app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java # app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java # app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java # app/src/main/res/values/strings.xml
This commit is contained in:
commit
35dbbd6a31
17
.gitattributes
vendored
Normal file
17
.gitattributes
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
* text=auto
|
||||
|
||||
# Explicitly declare text files you want to always be normalized and converted
|
||||
# to native line endings on checkout.
|
||||
*.java text
|
||||
*.gradle text
|
||||
*.md text
|
||||
*.properties text
|
||||
*.xml text
|
||||
|
||||
# Declare files that will always have CRLF line endings on checkout.
|
||||
#*.sln text eol=crlf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
#*.png binary
|
||||
#*.jpg binary
|
16
.github/ISSUE_TEMPLATE.md
vendored
16
.github/ISSUE_TEMPLATE.md
vendored
@ -1,10 +1,22 @@
|
||||
#### Before opening an issue please confirm the following:
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
#### Before reporting a bug, please confirm the following:
|
||||
- [ ] I have read the [wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] I have searched the [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] If you upload an image or other content, please make sure you have read and understood the [github policies and terms of services](https://help.github.com/articles/github-terms-of-service/#1-responsibility-for-user-generated-content)
|
||||
|
||||
### I got Gadgetbridge from:
|
||||
* [ ] F-Droid
|
||||
* [ ] I built it myself from source code (specify tag / commit)
|
||||
|
||||
If you got it from Google Play, please note [that version](https://github.com/TaaviE/Gadgetbridge) is unofficial and not supported here; it's also often quite outdated. Please switch to one of the above versions if you can.
|
||||
|
||||
#### Your issue is:
|
||||
*In case of a bug, do not forget to attach logs!*
|
||||
*If possible, please attach [logs](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Log-Files)! that might help identifying the problem.*
|
||||
|
||||
#### Your wearable device is:
|
||||
|
||||
|
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -9,6 +9,12 @@ about: Create a report to help us improve
|
||||
- [ ] I have searched the [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
|
||||
- [ ] If you upload an image or other content, please make sure you have read and understood the [github policies and terms of services](https://help.github.com/articles/github-terms-of-service/#1-responsibility-for-user-generated-content)
|
||||
|
||||
### I got Gadgetbridge from:
|
||||
* [ ] F-Droid
|
||||
* [ ] I built it myself from source code (specify tag / commit)
|
||||
|
||||
If you got it from Google Play, please note [that version](https://github.com/TaaviE/Gadgetbridge) is unofficial and not supported here; it's also often quite outdated. Please switch to one of the above versions if you can.
|
||||
|
||||
#### Your issue is:
|
||||
*If possible, please attach [logs](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Log-Files)! that might help identifying the problem.*
|
||||
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -31,3 +31,6 @@ proguard/
|
||||
MPChartLib
|
||||
|
||||
fw.dirs
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.classpath
|
||||
|
2
.settings/org.eclipse.buildship.core.prefs
Normal file
2
.settings/org.eclipse.buildship.core.prefs
Normal file
@ -0,0 +1,2 @@
|
||||
connection.project.dir=
|
||||
eclipse.preferences.version=1
|
@ -1,3 +1,6 @@
|
||||
os: linux
|
||||
dist: trusty
|
||||
|
||||
language: android
|
||||
|
||||
jdk:
|
||||
@ -16,10 +19,10 @@ android:
|
||||
- tools
|
||||
|
||||
# The BuildTools version used by your project
|
||||
- build-tools-27.0.3
|
||||
- build-tools-28.0.3
|
||||
|
||||
# The SDK version used to compile your project
|
||||
- android-27
|
||||
- android-28
|
||||
|
||||
# Additional components
|
||||
- extra-android-m2repository
|
||||
@ -31,7 +34,7 @@ android:
|
||||
#- sys-img-x86-android-17
|
||||
|
||||
before_install:
|
||||
- yes | sdkmanager "platforms;android-27"
|
||||
- yes | sdkmanager "platforms;android-28"
|
||||
|
||||
script:
|
||||
- ./gradlew build connectedCheck --stacktrace
|
||||
|
201
CHANGELOG.md
201
CHANGELOG.md
@ -1,5 +1,206 @@
|
||||
### Changelog
|
||||
|
||||
#### Version 0.40.1
|
||||
* Mi Band/Amazfit: Recogize changes when toggling alarm on device (immediately when connected, else when connecting)
|
||||
* Mi Band/Amazfit: Fix some bugs with stuck connection when re-connecting
|
||||
* Mi Band 4: Support higher MTU for multiple times faster firmware transfer (probably also Amazfit GTR/GTS)
|
||||
* Amazfit Cor: Fix setting language to Chinese manually
|
||||
|
||||
#### Version 0.40.0
|
||||
* Fossil Q Hybrid: Initial support
|
||||
* Bangle.js: Initial support
|
||||
* Reserve Alarm for Calendar feature restricted to Mi Band 1/2 and moved to per-device settings
|
||||
* New icon for App Manager
|
||||
|
||||
#### Version 0.39.1
|
||||
* Try to actively re-connect when a connection gets interrupted (interval grows up to 64 seconds)
|
||||
* Mi Band2/Amazfip Bip: Make button action settings per-device and enable for Amazfit Bip
|
||||
|
||||
#### Version 0.39.0
|
||||
* Amazfit GTS: Initial and incomplete support, mostly untested
|
||||
* Add forward/backward buttons to charts for faster navigation
|
||||
* Debug: allow to reset last fetch date for Huami devices
|
||||
|
||||
#### Version 0.38.0
|
||||
* Amazfit GTR: Initial and incomplete support, mostly untested
|
||||
* Amazfit Bip: add Portuguese to the list of selectable languages
|
||||
* Mi Band 4: Enable emoji font setting
|
||||
* Makibes HR3: Support the english version
|
||||
* Makibes HR3: Enable bluetooth pairing for working reconnection
|
||||
* Work around crash when trying to display changelog
|
||||
* Sleep detection settings: Rolling 24 hours (existing style) or Noon to noon
|
||||
* Add alternative color to heartrate in chart settings
|
||||
|
||||
#### Version 0.37.1
|
||||
* Amazfit Bip Lite: Support flashing firmware and watchfaces
|
||||
|
||||
#### Version 0.37.0
|
||||
* Initial Makibes HR3 support
|
||||
* Amazfit Bip Lite: Initial working support, firmware update is disabled for now (we do not have any firmware for testing)
|
||||
* Amazfit Cor 2: Enable Emoji Font setting and 3rd party HR access
|
||||
* Find Phone now also vibration in addition to playing the ring tone
|
||||
* ID115: All settings are now per-device
|
||||
* Time format settings are now per-device for all supported devices
|
||||
* Wrist location settings are now per-device for all supported devices
|
||||
* Work around broken layout in database management activity
|
||||
* Show toast in case no app is installed which can handle GPX files
|
||||
* Mi Band 4/Amazfit Bip Lite: Trim white spaces and new lines from auth key
|
||||
* Mi Band 4/Amazfit Bip Lite: Display a toast and do not try to pair if there was no auth key supplied
|
||||
* Skip service scan if supported device could be recognized without uuids during discovery
|
||||
|
||||
#### Version 0.36.2
|
||||
* Amazfit Bip: Untested support for Lite variant
|
||||
* Force Lineage OS to ask for permission when Trust is used to fix non-working incoming calls
|
||||
* Charts: List multiple sleep sessions per day
|
||||
|
||||
#### Version 0.36.1
|
||||
* Mi Band 2/3/4, Amazfit Bip/Cor: Add setting to expose the HR sensor to 3rd party apps
|
||||
* Mi Band 4: Really fix weather location not being updated on the Band
|
||||
* Mi Band 4: Fix call notifcation not stopping when call gets answered or rejected on the phone
|
||||
* Amazfit Bip/Cor: Support for custom emoji font
|
||||
* ZeTime: Enable emoji support
|
||||
* ZeTime: Make watch language the same as the phone language by default
|
||||
* New status and alarms widget
|
||||
* Fix crash when entering notification filter settings
|
||||
* Make diagram settings accessible from charts activity
|
||||
* Add option to hide the floating plus button in the main activity
|
||||
* Fix a potential crash on Android 4.4 KitKat
|
||||
|
||||
#### Version 0.36.0
|
||||
* Initial Mijia LYWSD02 support (Smart Clock with Humidity and Temperature Sensor), just for setting the time
|
||||
* Mi Band 3/4: Allow enabling the NFC menu where supported (useless for now)
|
||||
* Mi Band 3/4, Amazfit Cor/Bip: Set language immediately when changing it (not only on connect)
|
||||
* Mi Band 3/4, Amazfir Cor/Bip: Add icons for "swimming" and "exercise"
|
||||
* Mi Band 4: Support flashing the V2 font
|
||||
* Mi Band 4: Fix weather location not being updated on the Band
|
||||
* Mi Band 4: remove unsupported DND setting from settings menu
|
||||
* Amazfit Bip/Cor: Fix resetting of last fetched date for sports activities
|
||||
* Amazfit Bip: Fix sharing GPX files for some Apps
|
||||
* Pebble: Use Rebble Store URI
|
||||
* Support LineageOS 16.0 weather provider
|
||||
* Add Averages to Charts
|
||||
* Allow togging between weekly and monthly charts
|
||||
|
||||
#### Version 0.35.2
|
||||
* Mi Band 1/2: Crash when updating firmware while phone is set to Spanish
|
||||
* Mi Band 4: Enable music info support (displays now on the band)
|
||||
* Mi Band 4: Support setting date format (for built-in watchfaces)
|
||||
* Amazfit Cor 2: Try to fix empty menu on device
|
||||
|
||||
#### Version 0.35.1
|
||||
* Mi Band 4: Support flashing watchfaces, res and firmware (.ft untested)
|
||||
|
||||
#### Version 0.35.0
|
||||
* Mi Band 4: Initial support (WARNING: INITIAL SETUP NEEDS MI FIT WITH ACCOUNT AND ROOT, NOT A RECOMMENDED DEVICE FOR GADGETBRIDGE)
|
||||
|
||||
#### Version 0.34.1
|
||||
* Mi Band 1: Fix crash when entering per-device settings
|
||||
* Mi Band 3: Allow setting date format in per-device settings
|
||||
* ZeTime: Fix timestmaps
|
||||
* Fix a crash when flashing an non-whitelisted firmware while using Gadgetbridge in Spanish
|
||||
|
||||
#### Version 0.34.0
|
||||
* Mi Band 1/2/3/Bip/Cor: Migrate many settings to per-device settings (new settings icon in device card in main activity)
|
||||
* Mi Band 3: Fix setting menu items with 2.4 firmware and add support for the new timer menu
|
||||
* Amazfit Bip/Cor, Casio: Add support for muting incoming calls
|
||||
* ZeTime: Remove endless recursion in ZeTime settings
|
||||
* Recognize FairEmail notifications as generic email notifications
|
||||
|
||||
#### Version 0.33.1
|
||||
* Mi Band 3: Recognize "Xiaomi Band 3"
|
||||
* Amazfit Bip: Add German, Italian, French and Turkish to language settings
|
||||
|
||||
#### Version 0.33.0
|
||||
* BFH-16: Initial support
|
||||
* Mi Band 2/3/Bip/Cor: Generate random per-device security keys when pairing, allow manual override to still support multiple android devices connecting to the same device
|
||||
* Mi Band 3: Add Indonesian, Thai, Arabic, Vietnamese, Portuguese, Dutch, Turkish and Ukrainian to language settings
|
||||
* Mi Band 3: Support flashing latest Japanese-Korean font
|
||||
* Amazfit Cor 2: Initial experimental support (untested)
|
||||
* Pebble: Add pebblekit extension for reopening last app
|
||||
* Casio: Bugfixes and improvements
|
||||
* Lookup contacts also in work profile
|
||||
* Fix searching in application name when blacklisting
|
||||
* Remove misleading title from database management activity when no legacy database is available
|
||||
|
||||
#### Version 0.32.4
|
||||
* Make voip call support optional (disabled by default)
|
||||
* Amazfit Bip: GPX export corrections
|
||||
* ZeTime: Fix setting alarms
|
||||
* ZeTime: Fix wrong activity timestamps
|
||||
* ZeTime: Set HR alarm limits when changed, not only on connect
|
||||
* ZeTime: Sync preferences from the watch to Gadgetbridge settings
|
||||
|
||||
#### Version 0.32.3
|
||||
* Fix a crash in charts due to a broken German translation
|
||||
* Fix a crash when transliterating emoji
|
||||
* Amazfit Bip/Cor: Support disconnect notification (must be configured in Bip settings for Cor also for now)
|
||||
|
||||
#### Version 0.32.2
|
||||
* Fix setting alarms under some circumstances
|
||||
* Support calls notifications for some VoIP apps
|
||||
* Mi Band 3: Enable fetching sports activities (currently only useful for flushing activities)
|
||||
* Casio: Improve stability
|
||||
* Casio: Add explicit support for GB-6900B, GB-X6900B and GB-5600B
|
||||
|
||||
#### Version 0.32.1
|
||||
* Fix db deadlock on alarm migration
|
||||
|
||||
#### Version 0.32.0
|
||||
* Initial support for Casio GB-6900B
|
||||
* Increase number of alarms and store them per-device
|
||||
* Support factory reset in debug activity (Mi Band 1/2/3, Bip, Cor)
|
||||
* Filter out unicode control sequences (fixes problems with Telegram and probably others)
|
||||
* Fix endless loop resulting in OOM when RTL support is enabled
|
||||
* Recoginize p≡p as an email app
|
||||
* No longer display Android paired devices in that were not a paired with Gadgetbridge
|
||||
* Amazfit Bip: Allow flashing latest GPS firmware
|
||||
* Pebble: Native support for M7S watch face
|
||||
* No1 F1: Support for a Chinese clone
|
||||
|
||||
#### Version 0.31.3
|
||||
* Pebble: Fix crash with DISMISS and OPEN actions
|
||||
|
||||
#### Version 0.31.2
|
||||
* Pebble: Fix a regression that caused non-working mute, open and dismiss actions
|
||||
* Fix setting language to Czech manually
|
||||
* Ignore summary notification from K-9 Mail (caused notification spamming)
|
||||
|
||||
#### Version 0.31.1
|
||||
* Pebble: Fix crash when no canned replies have been set
|
||||
* Pebble: Let the firmware show localized default canned replies if none have been set
|
||||
* Amazfit Bip: Fix importing GPS tracks that have been recorded with Firmware 1.1.5.02
|
||||
* Display measured hr value in debug screen
|
||||
|
||||
#### Version 0.31.0
|
||||
* Pebble: Send all wearable notification actions (not only reply)
|
||||
* Pebble: Always allow reply action even if untested features are turned off
|
||||
* Pebble: Temporarily disable broken autoremove notification feature
|
||||
* Amazfit Bip: Allow flashing latest gps firmware (Mili_dth.gps)
|
||||
* Mi Band 3/Amazfit Bip/Amazfit Cor: Send Fahrenheit if units are set to imperial
|
||||
* Roidmi 3: Fix and enable support
|
||||
* Mi Band 3/Amazfit Bip: fix find phone crash
|
||||
* Prevent re-sending old notifications to the wearable
|
||||
* Enhancement and Fixes for Bengali Transliteration
|
||||
* Disable excessive logging in RTL support
|
||||
|
||||
#### Version 0.30.0
|
||||
* Amazfit Bip + Mi Band 3: Support for right to left display (configurable) (#976)
|
||||
* Add Arabic, Bengali Farsi, Persian, Scandinavian transliteration
|
||||
* Add support for some Roidmi FM receivers
|
||||
* Mi Band 3: Allow enabling the "Workout" menu item
|
||||
* Mi Band 3: Support for night mode configuration
|
||||
* Huami devices: fix seldom activity/sports synchronization problem (#1264)
|
||||
* Preferences: Make minimum heart rate configurable (lower values will be disregarded)
|
||||
* Preferences: Configure minimum time between notifications
|
||||
* Preferences: Group language settings
|
||||
* Attempt to fix BLE connection issues on Samsung S devices
|
||||
* Week sleep and steps charts: display balance (actual value vs. desired value)
|
||||
* Live Activity: show current/maximum heart rate, display minute steps and total steps and more improvements
|
||||
* Live Activity: fix discrepancy between number of steps in Gadgetbridge and wearable device
|
||||
* Fix missing caller ID for incoming calls on Android 9
|
||||
* Support for easy sharing of log files via the Debug screen
|
||||
* Misc small bugfixes
|
||||
|
||||
#### Version 0.29.1
|
||||
* Mi Band 3: Support setting language to to German, Italian, French, Polish, Japanese, Korean (read wiki)
|
||||
* Mi Band 3: Support flashing latest RES files
|
||||
|
@ -27,68 +27,90 @@
|
||||
* Daniele Gobbetti <daniele+github@gobbetti.name>
|
||||
* João Paulo Barraca <jpbarraca@gmail.com>
|
||||
* Yaron Shahrabani <sh.yaron@gmail.com>
|
||||
* Jonas <jonasdcdm@posteo.net>
|
||||
* Allan Nordhøy <epost@anotheragency.no>
|
||||
* postsorino <postsorino@krutt.org>
|
||||
* Jonas <jonasdcdm@posteo.net>
|
||||
* Roi Greenberg <roigreenberg@gmail.com>
|
||||
* Sebastian Kranz <tklightforce@googlemail.com>
|
||||
* Vadim Kaushan <admin@disasm.info>
|
||||
* Allan Nordhøy <epost@anotheragency.no>
|
||||
* protomors <protomors@gmail.com>
|
||||
* José Rebelo <joserebelo@outlook.com>
|
||||
* mesnevi <shams@airpost.net>
|
||||
* naofum <naofum@gmail.com>
|
||||
* youzhiran <2668760098@qq.com>
|
||||
* TaaviE <taavi.eomae+weblate@gmail.com>
|
||||
* mueller-ma <mueller-ma@users.noreply.github.com>
|
||||
* ivanovlev <ivanovlev@mail.ru>
|
||||
* naofum <naofum@gmail.com>
|
||||
* youzhiran <2668760098@qq.com>
|
||||
* Tijl Schepens <tijl.schepens@hotmail.com>
|
||||
* mesnevi <shams@airpost.net>
|
||||
* Hadrián Candela <hadrian.candela@gmail.com>
|
||||
* Julien Pivotto <roidelapluie@inuits.eu>
|
||||
* Andreas Böhler <dev@aboehler.at>
|
||||
* 陈少举 <oshirisu.red@gmail.com>
|
||||
* Taavi Eomäe <taavi.eomae+github@gmail.com>
|
||||
* Steffen Liebergeld <perl@gmx.org>
|
||||
* Lem Dulfo <lemuel.dulfo@gmail.com>
|
||||
* Hadrián Candela <hadrian.candela@gmail.com>
|
||||
* Felix Konstantin Maurer <maufl@maufl.de>
|
||||
* Sergey Trofimov <sarg@sarg.org.ru>
|
||||
* Robert Barat <rbarat07@gmail.com>
|
||||
* Pavel Elagin <pelagin@techcd.ru>
|
||||
* Lem Dulfo <lemuel.dulfo@gmail.com>
|
||||
* Matthieu Baerts <matttbe@gmail.com>
|
||||
* Felix Konstantin Maurer <maufl@maufl.de>
|
||||
* Utsob Roy <uroybd@gmail.com>
|
||||
* Sergey Trofimov <sarg@sarg.org.ru>
|
||||
* Full Name <petr+weblate@linuks.cz>
|
||||
* Robert Barat <rbarat07@gmail.com>
|
||||
* JohnnySun <bmy001@gmail.com>
|
||||
* Uwe Hermann <uwe@hermann-uwe.de>
|
||||
* Kranz <Kranz>
|
||||
* Gilles Émilien MOREL <contact@gilles-morel.fr>
|
||||
* Edoardo Rosa <edoardo.rosa90@gmail.com>
|
||||
* Bożydar <trening302@o2.pl>
|
||||
* Alberto <albertsal83@gmail.com>
|
||||
* Vladislav Serkov <vladserkoff@protonmail.com>
|
||||
* Vebryn <vebryn@gmail.com>
|
||||
* Gilles Émilien MOREL <contact@gilles-morel.fr>
|
||||
* M. Hadi <hhhadddi@yahoo.com>
|
||||
* Giuseppe Caliendo <giuseppe.caliendo@gmail.com>
|
||||
* Gergely Peidl <gergely@peidl.net>
|
||||
* Emre <wenigerpluesch@mailbox.org>
|
||||
* Bożydar <trening302@o2.pl>
|
||||
* Elwood <elwood21@gmail.com>
|
||||
* AndrewBedscastle <1462953+AndrewBedscastle@users.noreply.github.com>
|
||||
* abettenburg <a.bettenburg@gmail.com>
|
||||
* 0nse <0nse@users.noreply.github.com>
|
||||
* Максим Якимчук <xpinovo@gmail.com>
|
||||
* Rimas Raguliūnas <rarimas@gmail.com>
|
||||
* nautilusx <mail.ka@mailbox.org>
|
||||
* Minori Hiraoka (미노리) <minori@mnetwork.co.kr>
|
||||
* masakoodaa <masakoodaa@protonmail.com>
|
||||
* Marius Cornescu <marius_cornescu@yahoo.com>
|
||||
* Lukas Veneziano <fs@venezilu.de>
|
||||
* LL <lu.lecocq@free.fr>
|
||||
* Kompact <joaorafael123@hotmail.com>
|
||||
* K0L0B0G <github@gorobav.ru>
|
||||
* Johann C. Rode <jcrode@ece.ucsb.edu>
|
||||
* Jasper <jespiex456@hotmail.com>
|
||||
* Dikay900 <dark900@xyz.de>
|
||||
* Christian Fischer <sw-dev@computerlyrik.de>
|
||||
* c4ndel4 <hadrian.candela@gmail.com>
|
||||
* 6arms1leg <m.brnsfld@googlemail.com>
|
||||
* Zhong Jianxin <azuwis@gmail.com>
|
||||
* walkjivefly <mark@walkjivefly.com>
|
||||
* WaldiS <admin@sto.ugu.pl>
|
||||
* Thomas <tutonis@gmail.com>
|
||||
* Ted Stein <me@tedstein.net>
|
||||
* ssantos <ssantos@web.de>
|
||||
* Sebastian Obrusiewicz <sobrus2@o2.pl>
|
||||
* Ranved Sticon <the7bulk@gmail.com>
|
||||
* petronovak <petro.novak@gmail.com>
|
||||
* Petr Kadlec <mormegil@centrum.cz>
|
||||
* Pascal <pascal.tannich@gmail.com>
|
||||
* NotAFIle <nota@notafile.com>
|
||||
* Normano64 <per.bergqwist@gmail.com>
|
||||
* NicoBuntu <nicolas__du95@hotmail.fr>
|
||||
* Minori Hiraoka (미노리) <minori@mnetwork.co.kr>
|
||||
* Moarc <aldwulf@gmail.com>
|
||||
* Michal Novotny <mignov@gmail.com>
|
||||
* Martin <ritualz@users.noreply.github.com>
|
||||
* LL <lu.lecocq@free.fr>
|
||||
* Louis-Marie Croisez <louis.croisez@gmail.com>
|
||||
* Jesús <zaagur@gmail.com>
|
||||
* Irul <wedesignthing@gmail.com>
|
||||
* HenRy <helge1o1o1@gmail.com>
|
||||
* exit-failure <hakrala@web.de>
|
||||
* Dreamwalker <aristojeff@gmail.com>
|
||||
* Denis <korden@sky-play.ru>
|
||||
* Avamander <Avamander@users.noreply.github.com>
|
||||
* AnthonyDiGirolamo <anthony.digirolamo@gmail.com>
|
||||
@ -98,26 +120,32 @@
|
||||
* Yar <yaroslav.isakov@gmail.com>
|
||||
* xzovy <caleb@caleb-cooper.net>
|
||||
* xphnx <xphnx@users.noreply.github.com>
|
||||
* Xavier RENE-CORAIL <xavier.renecorail@gmail.com>
|
||||
* Vitaliy Shuruta <vshuruta@gmail.com>
|
||||
* Vincèn PUJOL <vincen@vincen.org>
|
||||
* veecue <veecue@ventos.tk>
|
||||
* Tomer Rosenfeld <tomerosenfeld007@gmail.com>
|
||||
* Tomas Radej <tradej@redhat.com>
|
||||
* tiparega <11555126+tiparega@users.noreply.github.com>
|
||||
* Tarik Sekmen <tarik@ilixi.org>
|
||||
* Szymon Tomasz Stefanek <s.stefanek@gmail.com>
|
||||
* szilardx <15869670+szilardx@users.noreply.github.com>
|
||||
* Stan Gomin <stan@gomin.me>
|
||||
* SinMan <emilio.galvan@gmail.com>
|
||||
* Sergio Lopez <slp@sinrega.org>
|
||||
* S Dantas <dantasosteney@gmail.com>
|
||||
* Sami Alaoui <4ndroidgeek@gmail.com>
|
||||
* Roman Plevka <rplevka@redhat.com>
|
||||
* rober <rober@prtl.nodomain.net>
|
||||
* redking <redking974@gmail.com>
|
||||
* Quallenauge <Hamsi2k@freenet.de>
|
||||
* Pavel Motyrev <legioner.r@gmail.com>
|
||||
* Pavel <elagin.pasha@gmail.com>
|
||||
* Olexandr Nesterenko <olexn@ukr.net>
|
||||
* Nicolò Balzarotti <anothersms@gmail.com>
|
||||
* Natanael Arndt <arndtn@gmail.com>
|
||||
* Nabil BENDAFI <nabil@bendafi.fr>
|
||||
* Molnár Barnabás <nsd4rkn3ss@gmail.com>
|
||||
* Moarc <aldwulf@gmail.com>
|
||||
* Mike van Rossum <mike@vanrossum.net>
|
||||
* Michal Novak <michal.novak@post.cz>
|
||||
* michaelneu <git@michaeln.eu>
|
||||
@ -142,9 +170,14 @@
|
||||
* kalaee <alex.kalaee@gmail.com>
|
||||
* Joseph Kim <official.jkim@gmail.com>
|
||||
* jonnsoft <>
|
||||
* Johannes Tysiak <vinyl@users.sf.net>
|
||||
* jcrode <46062294+jcrode@users.noreply.github.com>
|
||||
* Jan Lolek <janlolek@seznam.cz>
|
||||
* Jakub Jelínek <jakub.jelinek@gmail.com>
|
||||
* Ivan <ivan_tizhanin@mail.ru>
|
||||
* Hüseyin Aslan <ha098784@gmail.com>
|
||||
* hr-sales <hericsonregis@hotmail.com>
|
||||
* Hirnchirurg <anonymous11@posteo.net>
|
||||
* Hasan Ammar <ammarh@gmail.com>
|
||||
* Grzegorz Dznsk <grantmlody96@gmail.com>
|
||||
* Gilles MOREL <contact@gilles-morel.fr>
|
||||
@ -152,7 +185,8 @@
|
||||
* Gabe Schrecker <gabe@pbrb.co.uk>
|
||||
* freezed-or-frozen <freezed.or.frozen@gmail.com>
|
||||
* Frank Slezak <KazWolfe@users.noreply.github.com>
|
||||
* Dreamwalker <aristojeff@gmail.com>
|
||||
* Francesco Franchina <cescus92@gmail.com>
|
||||
* Edoardo Tronconi <edoardo.tronconi@gmail.com>
|
||||
* Dougal19 <4662351+Dougal19@users.noreply.github.com>
|
||||
* Davis Mosenkovs <davikovs@gmail.com>
|
||||
* Daniel Hauck <maill@dhauck.eu>
|
||||
@ -164,14 +198,18 @@
|
||||
* Carlos Ferreira <calbertoferreira@gmail.com>
|
||||
* bucala <marcel.bucala@gmail.com>
|
||||
* boun <boun@gmx.de>
|
||||
* Benjamin Kahlau <nyhkkbjyek@roanapur.de>
|
||||
* batataspt@gmail.com <batataspt@gmail.com>
|
||||
* atkyritsis <at.kyritsis@gmail.com>
|
||||
* apre <adrienpre+github@gmail.com>
|
||||
* Aniruddha Adhikary <aniruddha@adhikary.net>
|
||||
* andrewlytvyn <indusfreelancer@gmail.com>
|
||||
* AndrewH <36428679+andrewheadricke@users.noreply.github.com>
|
||||
* andre <andre.buesgen@yahoo.de>
|
||||
* Allen B <28495335+Allen-B1@users.noreply.github.com>
|
||||
* Alfeu Lucas Guedes dos Santos <alfeugds@gmail.com>
|
||||
* Alexey Afanasev <avafanasiev@gmail.com>
|
||||
* Alexandra Sevostyanova <asevostyanova@gmail.com>
|
||||
|
||||
And all the Transifex translators, which I cannot automatically list, at the moment.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
apply plugin: 'java'
|
||||
//apply plugin: 'maven'
|
||||
apply plugin: 'maven'
|
||||
apply plugin:'application'
|
||||
|
||||
archivesBaseName = 'gadgetbridge-daogenerator'
|
||||
@ -8,7 +8,7 @@ archivesBaseName = 'gadgetbridge-daogenerator'
|
||||
dependencies {
|
||||
// compile 'org.greenrobot:greendao-generator:2.2.0'
|
||||
// compile project(":DaoGenerator")
|
||||
compile 'com.github.freeyourgadget:greendao:1998d7cd2d21f662c6044f6ccf3b3a251bbad341'
|
||||
compile 'com.github.Freeyourgadget:greendao:1998d7cd2d21f662c6044f6ccf3b3a251bbad341'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
@ -15,8 +15,6 @@
|
||||
*/
|
||||
package nodomain.freeyourgadget.gadgetbridge.daogen;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import de.greenrobot.daogenerator.DaoGenerator;
|
||||
import de.greenrobot.daogenerator.Entity;
|
||||
import de.greenrobot.daogenerator.Index;
|
||||
@ -45,7 +43,7 @@ public class GBDaoGenerator {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Schema schema = new Schema(19, MAIN_PACKAGE + ".entities");
|
||||
Schema schema = new Schema(22, MAIN_PACKAGE + ".entities");
|
||||
|
||||
Entity userAttributes = addUserAttributes(schema);
|
||||
Entity user = addUserInfo(schema, userAttributes);
|
||||
@ -60,6 +58,7 @@ public class GBDaoGenerator {
|
||||
Entity tag = addTag(schema);
|
||||
Entity userDefinedActivityOverlay = addActivityDescription(schema, tag, user);
|
||||
|
||||
addMakibesHR3ActivitySample(schema, user, device);
|
||||
addMiBandActivitySample(schema, user, device);
|
||||
addPebbleHealthActivitySample(schema, user, device);
|
||||
addPebbleHealthActivityKindOverlay(schema, user, device);
|
||||
@ -73,6 +72,11 @@ public class GBDaoGenerator {
|
||||
addID115ActivitySample(schema, user, device);
|
||||
addJYouActivitySample(schema, user, device);
|
||||
addCalendarSyncState(schema, device);
|
||||
addAlarms(schema, user, device);
|
||||
|
||||
Entity notificationFilter = addNotificationFilters(schema);
|
||||
|
||||
addNotificationFilterEntry(schema, notificationFilter);
|
||||
|
||||
addBipActivitySummary(schema, user, device);
|
||||
|
||||
@ -181,6 +185,16 @@ public class GBDaoGenerator {
|
||||
return deviceAttributes;
|
||||
}
|
||||
|
||||
private static Entity addMakibesHR3ActivitySample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "MakibesHR3ActivitySample");
|
||||
activitySample.implementsSerializable();
|
||||
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
|
||||
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
|
||||
addHeartRateProperties(activitySample);
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static Entity addMiBandActivitySample(Schema schema, Entity user, Entity device) {
|
||||
Entity activitySample = addEntity(schema, "MiBandActivitySample");
|
||||
activitySample.implementsSerializable();
|
||||
@ -355,6 +369,55 @@ public class GBDaoGenerator {
|
||||
calendarSyncState.addIntProperty("hash").notNull();
|
||||
}
|
||||
|
||||
private static void addAlarms(Schema schema, Entity user, Entity device) {
|
||||
Entity alarm = addEntity(schema, "Alarm");
|
||||
alarm.implementsInterface("nodomain.freeyourgadget.gadgetbridge.model.Alarm");
|
||||
Property deviceId = alarm.addLongProperty("deviceId").notNull().getProperty();
|
||||
Property userId = alarm.addLongProperty("userId").notNull().getProperty();
|
||||
Property position = alarm.addIntProperty("position").notNull().getProperty();
|
||||
Index indexUnique = new Index();
|
||||
indexUnique.addProperty(deviceId);
|
||||
indexUnique.addProperty(userId);
|
||||
indexUnique.addProperty(position);
|
||||
indexUnique.makeUnique();
|
||||
alarm.addIndex(indexUnique);
|
||||
alarm.addBooleanProperty("enabled").notNull();
|
||||
alarm.addBooleanProperty("smartWakeup").notNull();
|
||||
alarm.addIntProperty("repetition").notNull().codeBeforeGetter(
|
||||
"public boolean isRepetitive() { return getRepetition() != ALARM_ONCE; } " +
|
||||
"public boolean getRepetition(int dow) { return (this.repetition & dow) > 0; }"
|
||||
);
|
||||
alarm.addIntProperty("hour").notNull();
|
||||
alarm.addIntProperty("minute").notNull();
|
||||
alarm.addBooleanProperty("unused").notNull();
|
||||
alarm.addToOne(user, userId);
|
||||
alarm.addToOne(device, deviceId);
|
||||
}
|
||||
|
||||
private static void addNotificationFilterEntry(Schema schema, Entity notificationFilterEntity) {
|
||||
Entity notificatonFilterEntry = addEntity(schema, "NotificationFilterEntry");
|
||||
notificatonFilterEntry.addIdProperty().autoincrement();
|
||||
Property notificationFilterId = notificatonFilterEntry.addLongProperty("notificationFilterId").notNull().getProperty();
|
||||
notificatonFilterEntry.addStringProperty("notificationFilterContent").notNull().getProperty();
|
||||
notificatonFilterEntry.addToOne(notificationFilterEntity, notificationFilterId);
|
||||
}
|
||||
|
||||
private static Entity addNotificationFilters(Schema schema) {
|
||||
Entity notificatonFilter = addEntity(schema, "NotificationFilter");
|
||||
Property appIdentifier = notificatonFilter.addStringProperty("appIdentifier").notNull().getProperty();
|
||||
|
||||
notificatonFilter.addIdProperty().autoincrement();
|
||||
|
||||
Index indexUnique = new Index();
|
||||
indexUnique.addProperty(appIdentifier);
|
||||
indexUnique.makeUnique();
|
||||
notificatonFilter.addIndex(indexUnique);
|
||||
|
||||
Property notificationFilterMode = notificatonFilter.addIntProperty("notificationFilterMode").notNull().getProperty();
|
||||
Property notificationFilterSubMode = notificatonFilter.addIntProperty("notificationFilterSubMode").notNull().getProperty();
|
||||
return notificatonFilter;
|
||||
}
|
||||
|
||||
private static void addBipActivitySummary(Schema schema, Entity user, Entity device) {
|
||||
Entity summary = addEntity(schema, "BaseActivitySummary");
|
||||
summary.implementsInterface(ACTIVITY_SUMMARY);
|
||||
|
103
README.md
103
README.md
@ -1,3 +1,5 @@
|
||||
Gadgetbridge is now hosted on [codeberg.org](https://codeberg.org/Freeyourgadget/Gadgetbridge/)
|
||||
|
||||
Gadgetbridge
|
||||
============
|
||||
|
||||
@ -15,75 +17,48 @@ vendor's servers.
|
||||
|
||||
|
||||
[![Build](https://travis-ci.org/Freeyourgadget/Gadgetbridge.svg?branch=master)](https://travis-ci.org/Freeyourgadget/Gadgetbridge)
|
||||
[![Code Quality: Java](https://img.shields.io/lgtm/grade/java/g/Freeyourgadget/Gadgetbridge.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Freeyourgadget/Gadgetbridge/context:java)
|
||||
[![Total Alerts](https://img.shields.io/lgtm/alerts/g/Freeyourgadget/Gadgetbridge.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Freeyourgadget/Gadgetbridge/alerts)
|
||||
[![Translate](https://hosted.weblate.org/widgets/freeyourgadget/-/gadgetbridge/svg-badge.svg)](https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge)
|
||||
|
||||
## Download
|
||||
|
||||
[<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="80">](https://f-droid.org/app/nodomain.freeyourgadget.gadgetbridge)
|
||||
|
||||
[List of changes](https://github.com/Freeyourgadget/Gadgetbridge/blob/master/CHANGELOG.md)
|
||||
[List of changes](https://codeberg.org/Freeyourgadget/Gadgetbridge/src/master/CHANGELOG.md)
|
||||
|
||||
## Supported Devices
|
||||
* Amazfit Bip [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip)
|
||||
* Amazfit Cor (no maintainer) [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Cor)
|
||||
* HPlus Devices (e.g. ZeBand) [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/HPlus)
|
||||
* ID115 (WIP)
|
||||
* Lenovo Watch 9 (WIP)
|
||||
* Liveview (WIP)
|
||||
* Mi Band, Mi Band 1A, Mi Band 1S [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band)
|
||||
* Mi Band 2 [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-2)
|
||||
* Mi Band 3 [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-3)
|
||||
* NO.1 F1 (WIP)
|
||||
* Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, Pebble Time Round [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble)
|
||||
* Pebble 2 [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble)
|
||||
* Teclast H10, H30 (WIP)
|
||||
## Supported Devices (Some of them WIP and some of them without maintainer)
|
||||
* Amazfit Bip [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip)
|
||||
* Amazfit Bip Lite (NOT RECOMMENDED, NEEDS MI FIT WITH ACCOUNT ONCE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip-Lite)
|
||||
* Amazfit Cor [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Cor)
|
||||
* Amazfit Cor 2 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Cor-2)
|
||||
* Amazfit GTR (NOT RECOMMENDED, NEEDS MI FIT WITH ACCOUNT ONCE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTR)
|
||||
* Amazfit GTS (NOT RECOMMENDED, NEEDS MI FIT WITH ACCOUNT ONCE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Amazfit-GTS)
|
||||
* BFH-16
|
||||
* Casio GB-6900B
|
||||
* Fossil Q Hybrid
|
||||
* HPlus Devices (e.g. ZeBand) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/HPlus)
|
||||
* ID115
|
||||
* Lenovo Watch 9
|
||||
* Liveview
|
||||
* Makibes HR3
|
||||
* Mi Band, Mi Band 1A, Mi Band 1S [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band)
|
||||
* Mi Band 2 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-2)
|
||||
* Mi Band 3 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-3)
|
||||
* Mi Band 4 (NOT RECOMMENDED, NEEDS MI FIT WITH ACCOUNT AND ROOT ONCE) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-4)
|
||||
* Mi Scale 2 (currently only displays a toast after stepping on the scale)
|
||||
* NO.1 F1
|
||||
* Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, Pebble Time Round [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Pebble)
|
||||
* Pebble 2 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Pebble)
|
||||
* Teclast H10, H30
|
||||
* XWatch (Affordable Chinese Casio-like smartwatches)
|
||||
* Vibratissimo (experimental)
|
||||
* ZeTime (WIP)
|
||||
* ZeTime [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/MyKronoz-ZeTime)
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
Please see [FEATURES.md](https://github.com/Freeyourgadget/Gadgetbridge/blob/master/FEATURES.md)
|
||||
|
||||
## Getting Started (Pebble)
|
||||
|
||||
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
|
||||
|
||||
For more information read [this wiki article](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble-Getting-Started)
|
||||
|
||||
## How to use (Mi Band 1+2)
|
||||
|
||||
* 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 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 Band item to connect if you're not connected yet
|
||||
4. To test, chose "Debug" from the menu and play around
|
||||
|
||||
**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.
|
||||
* In order to display text notifications on the Mi Band 2, you have to [install a font on the band](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-2).
|
||||
|
||||
## Features (Liveview)
|
||||
|
||||
* set time (automatically upon connection)
|
||||
* display notifications and vibrate
|
||||
Please see [FEATURES.md](https://codeberg.org/Freeyourgadget/Gadgetbridge/src/master/FEATURES.md)
|
||||
|
||||
## Authors
|
||||
### Core Team (in order of first code contribution)
|
||||
@ -93,7 +68,6 @@ For more information read [this wiki article](https://github.com/Freeyourgadget/
|
||||
* Daniele Gobbetti
|
||||
|
||||
### Additional device support
|
||||
|
||||
* João Paulo Barraca (HPlus)
|
||||
* Vitaly Svyastyn (NO.1 F1)
|
||||
* Sami Alaoui (Teclast H30)
|
||||
@ -101,11 +75,17 @@ For more information read [this wiki article](https://github.com/Freeyourgadget/
|
||||
* Sebastian Kranz (ZeTime)
|
||||
* Vadim Kaushan (ID115)
|
||||
* "maxirnilian" (Lenovo Watch 9)
|
||||
* Andreas Böhler (Casio GB-6900B)
|
||||
* Jean-François Greffier (Mi Scale 2)
|
||||
* Johannes Schmitt (BFH-16)
|
||||
* Lukas Schwichtenberg (Makibes HR3)
|
||||
* Daniel Dakhno (Fossil Q Hybrid)
|
||||
* Gordon Williams (Bangle.js)
|
||||
|
||||
## Contribute
|
||||
|
||||
Contributions are welcome, be it feedback, bug reports, 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);
|
||||
on any of the open [issues](https://codeberg.org/Freeyourgadget/Gadgetbridge/issues);
|
||||
just leave a comment that you're working on one to avoid duplicated work.
|
||||
|
||||
Translations can be contributed via https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/
|
||||
@ -116,6 +96,7 @@ 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.
|
||||
- Do not ask for help regarding your own projects, unless they are Gadgetbridge related
|
||||
|
||||
## Having problems?
|
||||
|
||||
@ -123,7 +104,7 @@ Feel free to open an issue on our issue tracker, but please:
|
||||
1. Open Gadgetbridge's settings and check the option to write log files
|
||||
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
|
||||
4. File an issue at https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/new and possibly provide the logfile
|
||||
|
||||
Alternatively you may use the standard logcat functionality to access the log.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: "findbugs"
|
||||
apply plugin: "com.github.spotbugs"
|
||||
apply plugin: "pmd"
|
||||
|
||||
def ABORT_ON_CHECK_FAILURE = false
|
||||
@ -16,8 +16,8 @@ android {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion "27.0.3"
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "nodomain.freeyourgadget.gadgetbridge"
|
||||
@ -25,8 +25,8 @@ android {
|
||||
targetSdkVersion 27
|
||||
|
||||
// Note: always bump BOTH versionCode and versionName!
|
||||
versionName "0.29.1"
|
||||
versionCode 137
|
||||
versionName "0.40.1"
|
||||
versionCode 164
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
buildTypes {
|
||||
@ -60,33 +60,38 @@ pmd {
|
||||
dependencies {
|
||||
// testImplementation "ch.qos.logback:logback-classic:1.1.3"
|
||||
// testImplementation "ch.qos.logback:logback-core:1.1.3"
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
testImplementation "junit:junit:4.12"
|
||||
testImplementation "org.mockito:mockito-core:1.10.19"
|
||||
testImplementation "org.robolectric:robolectric:3.6.1"
|
||||
testImplementation "org.robolectric:robolectric:4.2.1"
|
||||
testImplementation "com.google.code.gson:gson:2.8.5"
|
||||
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "com.android.support:appcompat-v7:27.1.1"
|
||||
implementation "com.android.support:cardview-v7:27.1.1"
|
||||
implementation "com.android.support:recyclerview-v7:27.1.1"
|
||||
implementation "com.android.support:support-v4:27.1.1"
|
||||
implementation "com.android.support:gridlayout-v7:27.1.1"
|
||||
implementation "com.android.support:design:27.1.1"
|
||||
implementation "com.android.support:palette-v7:27.1.1"
|
||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||
implementation "androidx.preference:preference:1.1.0"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
||||
implementation "com.google.android.material:material:1.0.0"
|
||||
implementation "androidx.palette:palette:1.0.0"
|
||||
implementation("com.github.tony19:logback-android-classic:1.1.1-6") {
|
||||
exclude group: "com.google.android", module: "android"
|
||||
}
|
||||
implementation "org.slf4j:slf4j-api:1.7.12"
|
||||
implementation "com.github.Freeyourgadget:MPAndroidChart:5e5bd6c1d3e95c515d4853647ae554e48ee1d593"
|
||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||
implementation "com.github.pfichtner:durationformatter:0.1.1"
|
||||
implementation "de.cketti.library.changelog:ckchangelog:1.2.2"
|
||||
implementation "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.
|
||||
implementation "org.greenrobot:greendao:2.2.1"
|
||||
implementation "org.apache.commons:commons-lang3:3.5"
|
||||
implementation "org.apache.commons:commons-lang3:3.7"
|
||||
implementation "org.cyanogenmod:platform.sdk:6.0"
|
||||
implementation 'com.jaredrummler:colorpicker:1.0.2'
|
||||
// implementation project(":DaoCore")
|
||||
implementation 'com.github.wax911:android-emojify:0.1.7'
|
||||
|
||||
}
|
||||
|
||||
preBuild.dependsOn(":GBDaoGenerator:genSources")
|
||||
@ -95,7 +100,7 @@ gradle.beforeProject {
|
||||
preBuild.dependsOn(":GBDaoGenerator:genSources")
|
||||
}
|
||||
|
||||
check.dependsOn "findbugs", "pmd", "lint"
|
||||
check.dependsOn "spotbugsMain", "pmd", "lint"
|
||||
|
||||
task pmd(type: Pmd) {
|
||||
ruleSetFiles = files("${project.rootDir}/config/pmd/pmd-ruleset.xml")
|
||||
@ -130,30 +135,40 @@ task pmd(type: Pmd) {
|
||||
xml.enabled = false
|
||||
html.enabled = true
|
||||
xml {
|
||||
destination "$project.buildDir/reports/pmd/pmd.xml"
|
||||
destination file("$project.buildDir/reports/pmd/pmd.xml")
|
||||
}
|
||||
html {
|
||||
destination "$project.buildDir/reports/pmd/pmd.html"
|
||||
destination file("$project.buildDir/reports/pmd/pmd.html")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task findbugs(type: FindBugs) {
|
||||
// this is just for spotbugs to let the plugin create the task
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs = []
|
||||
}
|
||||
}
|
||||
|
||||
spotbugs {
|
||||
toolVersion = "3.1.12"
|
||||
ignoreFailures = !ABORT_ON_CHECK_FAILURE
|
||||
effort = "default"
|
||||
reportLevel = "medium"
|
||||
}
|
||||
|
||||
tasks.withType(com.github.spotbugs.SpotBugsTask) {
|
||||
source = fileTree('src/main/java')
|
||||
classes = files("${project.rootDir}/app/build/intermediates/javac/debug/classes")
|
||||
excludeFilter = new File("${project.rootDir}/config/findbugs/findbugs-filter.xml")
|
||||
classes = files("${project.rootDir}/app/build/intermediates/classes")
|
||||
source = fileTree("src/main/java/")
|
||||
classpath = files()
|
||||
reports {
|
||||
xml.enabled = false
|
||||
html.enabled = true
|
||||
xml {
|
||||
destination "$project.buildDir/reports/findbugs/findbugs-output.xml"
|
||||
destination file ("$project.buildDir/reports/spotbugs/spotbugs-output.xml")
|
||||
}
|
||||
html {
|
||||
destination "$project.buildDir/reports/findbugs/findbugs-output.html"
|
||||
destination file ("$project.buildDir/reports/spotbugs/spotbugs-output.html")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="src" path="java"/>
|
||||
<classpathentry kind="src" path="aidl"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>Gadgetbridge</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -1,12 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
@ -20,9 +20,13 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" /> <!-- Used for reverse find device -->
|
||||
|
||||
<uses-permission android:name="cyanogenmod.permission.ACCESS_WEATHER_MANAGER" />
|
||||
<uses-permission android:name="cyanogenmod.permission.READ_WEATHER" />
|
||||
<uses-permission android:name="lineageos.permission.ACCESS_WEATHER_MANAGER" />
|
||||
<uses-permission android:name="lineageos.permission.READ_WEATHER" />
|
||||
<uses-permission android:name="org.omnirom.omnijaws.READ_WEATHER" />
|
||||
|
||||
<uses-feature
|
||||
@ -57,10 +61,18 @@
|
||||
android:name=".activities.SettingsActivity"
|
||||
android:label="@string/title_activity_settings"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.charts.ChartsPreferencesActivity"
|
||||
android:label="@string/activity_prefs_charts"
|
||||
android:parentActivityName=".activities.charts.ChartsPreferencesActivity" />
|
||||
<activity
|
||||
android:name=".devices.miband.MiBandPreferencesActivity"
|
||||
android:label="@string/preferences_miband_settings"
|
||||
android:parentActivityName=".activities.SettingsActivity" />
|
||||
<activity
|
||||
android:name=".devices.zetime.ZeTimePreferenceActivity"
|
||||
android:label="@string/zetime_title_settings"
|
||||
android:parentActivityName=".activities.SettingsActivity" />
|
||||
<activity
|
||||
android:name=".activities.ActivitySummariesActivity"
|
||||
android:label="@string/activity_summaries"
|
||||
@ -401,16 +413,24 @@
|
||||
<activity
|
||||
android:name=".activities.ConfigureAlarms"
|
||||
android:label="@string/title_activity_set_alarm"
|
||||
android:parentActivityName=".activities.SettingsActivity" />
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.devicesettings.DeviceSettingsActivity"
|
||||
android:label="@string/title_activity_device_specific_settings" />
|
||||
<activity
|
||||
android:name=".activities.AlarmDetails"
|
||||
android:label="@string/title_activity_alarm_details"
|
||||
android:screenOrientation="portrait"
|
||||
android:parentActivityName=".activities.ConfigureAlarms" />
|
||||
android:parentActivityName=".activities.ConfigureAlarms"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".activities.VibrationActivity"
|
||||
android:label="@string/title_activity_vibration"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.NotificationFilterActivity"
|
||||
android:label="@string/title_activity_notification_filter"
|
||||
android:windowSoftInputMode="stateHidden|adjustPan"
|
||||
android:parentActivityName=".activities.AppBlacklistActivity" />
|
||||
<activity
|
||||
android:name=".activities.FindPhoneActivity"
|
||||
android:label="Find Phone" />
|
||||
@ -421,7 +441,7 @@
|
||||
android:exported="true" />
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.screenshot_provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
@ -430,7 +450,8 @@
|
||||
android:resource="@xml/shared_paths" />
|
||||
</provider>
|
||||
|
||||
<receiver android:name=".SleepAlarmWidget">
|
||||
<receiver android:name=".SleepAlarmWidget"
|
||||
android:label="@string/appwidget_sleep_alarm_widget_label">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
<action android:name="nodomain.freeyourgadget.gadgetbridge.SLEEP_ALARM_WIDGET_CLICK" />
|
||||
@ -441,6 +462,26 @@
|
||||
android:resource="@xml/sleep_alarm_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".Widget"
|
||||
android:label="@string/widget_listing_label">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
<action android:name="nodomain.freeyourgadget.gadgetbridge.WidgetClick" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_info" />
|
||||
</receiver>
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".activities.WidgetAlarmsActivity"
|
||||
android:launchMode="singleInstance"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog"
|
||||
android:excludeFromRecents="true"/>
|
||||
|
||||
<activity
|
||||
android:launchMode="singleTask"
|
||||
android:allowTaskReparenting="true"
|
||||
@ -458,7 +499,12 @@
|
||||
<data android:scheme="gadgetbridge" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".devices.qhybrid.ConfigActivity"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name=".devices.qhybrid.QHybridAppChoserActivity"
|
||||
android:exported="true" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weather;
|
||||
|
||||
import lineageos.weather.IWeatherServiceProviderChangeListener;
|
||||
import lineageos.weather.RequestInfo;
|
||||
|
||||
interface ILineageWeatherManager {
|
||||
oneway void updateWeather(in RequestInfo info);
|
||||
oneway void lookupCity(in RequestInfo info);
|
||||
oneway void registerWeatherServiceProviderChangeListener(
|
||||
in IWeatherServiceProviderChangeListener listener);
|
||||
oneway void unregisterWeatherServiceProviderChangeListener(
|
||||
in IWeatherServiceProviderChangeListener listener);
|
||||
String getActiveWeatherServiceProviderLabel();
|
||||
oneway void cancelRequest(int requestId);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weather;
|
||||
|
||||
import lineageos.weather.RequestInfo;
|
||||
import lineageos.weather.WeatherInfo;
|
||||
import lineageos.weather.WeatherLocation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
interface IRequestInfoListener {
|
||||
void onWeatherRequestCompleted(in RequestInfo requestInfo, int status,
|
||||
in WeatherInfo weatherInfo);
|
||||
void onLookupCityRequestCompleted(in RequestInfo requestInfo, int status,
|
||||
in List<WeatherLocation> weatherLocation);
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weather;
|
||||
|
||||
/** @hide */
|
||||
oneway interface IWeatherServiceProviderChangeListener {
|
||||
void onWeatherServiceProviderChanged(String providerLabel);
|
||||
}
|
19
app/src/main/aidl/lineageos/weather/RequestInfo.aidl
Normal file
19
app/src/main/aidl/lineageos/weather/RequestInfo.aidl
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weather;
|
||||
|
||||
parcelable RequestInfo;
|
19
app/src/main/aidl/lineageos/weather/WeatherInfo.aidl
Normal file
19
app/src/main/aidl/lineageos/weather/WeatherInfo.aidl
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanongenMod 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 lineageos.weather;
|
||||
|
||||
parcelable WeatherInfo;
|
19
app/src/main/aidl/lineageos/weather/WeatherLocation.aidl
Normal file
19
app/src/main/aidl/lineageos/weather/WeatherLocation.aidl
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weather;
|
||||
|
||||
parcelable WeatherLocation;
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weatherservice;
|
||||
|
||||
import lineageos.weatherservice.IWeatherProviderServiceClient;
|
||||
import lineageos.weather.RequestInfo;
|
||||
|
||||
interface IWeatherProviderService {
|
||||
void processWeatherUpdateRequest(in RequestInfo request);
|
||||
void processCityNameLookupRequest(in RequestInfo request);
|
||||
void setServiceClient(in IWeatherProviderServiceClient client);
|
||||
void cancelOngoingRequests();
|
||||
void cancelRequest(int requestId);
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weatherservice;
|
||||
|
||||
import lineageos.weather.RequestInfo;
|
||||
import lineageos.weatherservice.ServiceRequestResult;
|
||||
|
||||
interface IWeatherProviderServiceClient {
|
||||
void setServiceRequestState(in RequestInfo requestInfo, in ServiceRequestResult result,
|
||||
int state);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weatherservice;
|
||||
|
||||
parcelable ServiceRequestResult;
|
@ -9,7 +9,7 @@ navigator.geolocation.getCurrentPosition = function(success, failure, options) {
|
||||
reportedPositionFailures = 0;
|
||||
success(geoposition);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (window.Storage){
|
||||
var prefix = GBjs.getAppLocalstoragePrefix();
|
||||
|
31
app/src/main/java/lineageos/app/LineageContextConstants.java
Normal file
31
app/src/main/java/lineageos/app/LineageContextConstants.java
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2015, The CyanogenMod 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 lineageos.app;
|
||||
|
||||
|
||||
public final class LineageContextConstants {
|
||||
|
||||
private LineageContextConstants() {
|
||||
// Empty constructor
|
||||
}
|
||||
|
||||
public static final String LINEAGE_WEATHER_SERVICE = "lineageweather";
|
||||
|
||||
public static class Features {
|
||||
public static final String WEATHER_SERVICES = "org.lineageos.weather";
|
||||
}
|
||||
}
|
32
app/src/main/java/lineageos/os/Build.java
Normal file
32
app/src/main/java/lineageos/os/Build.java
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The CyanogenMod 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 lineageos.os;
|
||||
|
||||
|
||||
public class Build {
|
||||
public static class LINEAGE_VERSION_CODES {
|
||||
public static final int APRICOT = 1;
|
||||
public static final int BOYSENBERRY = 2;
|
||||
public static final int CANTALOUPE = 3;
|
||||
public static final int DRAGON_FRUIT = 4;
|
||||
public static final int ELDERBERRY = 5;
|
||||
public static final int FIG = 6;
|
||||
public static final int GUAVA = 7;
|
||||
public static final int HACKBERRY = 8;
|
||||
public static final int ILAMA = 9;
|
||||
}
|
||||
}
|
153
app/src/main/java/lineageos/os/Concierge.java
Normal file
153
app/src/main/java/lineageos/os/Concierge.java
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.os;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import lineageos.os.Build.LINEAGE_VERSION_CODES;
|
||||
|
||||
/**
|
||||
* Simply, Concierge handles your parcels and makes sure they get marshalled and unmarshalled
|
||||
* correctly when cross IPC boundaries even when there is a version mismatch between the client
|
||||
* sdk level and the framework implementation.
|
||||
*
|
||||
* <p>On incoming parcel (to be unmarshalled):
|
||||
*
|
||||
* <pre class="prettyprint">
|
||||
* ParcelInfo incomingParcelInfo = Concierge.receiveParcel(incomingParcel);
|
||||
* int parcelableVersion = incomingParcelInfo.getParcelVersion();
|
||||
*
|
||||
* // Do unmarshalling steps here iterating over every plausible version
|
||||
*
|
||||
* // Complete the process
|
||||
* incomingParcelInfo.complete();
|
||||
* </pre>
|
||||
*
|
||||
* <p>On outgoing parcel (to be marshalled):
|
||||
*
|
||||
* <pre class="prettyprint">
|
||||
* ParcelInfo outgoingParcelInfo = Concierge.prepareParcel(incomingParcel);
|
||||
*
|
||||
* // Do marshalling steps here iterating over every plausible version
|
||||
*
|
||||
* // Complete the process
|
||||
* outgoingParcelInfo.complete();
|
||||
* </pre>
|
||||
*/
|
||||
public final class Concierge {
|
||||
|
||||
/** Not instantiable */
|
||||
private Concierge() {
|
||||
// Don't instantiate
|
||||
}
|
||||
|
||||
/**
|
||||
* Since there might be a case where new versions of the lineage framework use applications running
|
||||
* old versions of the protocol (and thus old versions of this class), we need a versioning
|
||||
* system for the parcels sent between the core framework and its sdk users.
|
||||
*
|
||||
* This parcelable version should be the latest version API version listed in
|
||||
* {@link LINEAGE_VERSION_CODES}
|
||||
* @hide
|
||||
*/
|
||||
public static final int PARCELABLE_VERSION = LINEAGE_VERSION_CODES.ILAMA;
|
||||
|
||||
/**
|
||||
* Tell the concierge to receive our parcel, so we can get information from it.
|
||||
*
|
||||
* MUST CALL {@link ParcelInfo#complete()} AFTER UNMARSHALLING.
|
||||
*
|
||||
* @param parcel Incoming parcel to be unmarshalled
|
||||
* @return {@link ParcelInfo} containing parcel information, specifically the version.
|
||||
*/
|
||||
public static ParcelInfo receiveParcel(Parcel parcel) {
|
||||
return new ParcelInfo(parcel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a parcel for the Concierge.
|
||||
*
|
||||
* MUST CALL {@link ParcelInfo#complete()} AFTER MARSHALLING.
|
||||
*
|
||||
* @param parcel Outgoing parcel to be marshalled
|
||||
* @return {@link ParcelInfo} containing parcel information, specifically the version.
|
||||
*/
|
||||
public static ParcelInfo prepareParcel(Parcel parcel) {
|
||||
return new ParcelInfo(parcel, PARCELABLE_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parcel header info specific to the Parcel object that is passed in via
|
||||
* {@link #prepareParcel(Parcel)} or {@link #receiveParcel(Parcel)}. The exposed method
|
||||
* of {@link #getParcelVersion()} gets the api level of the parcel object.
|
||||
*/
|
||||
public final static class ParcelInfo {
|
||||
private Parcel mParcel;
|
||||
private int mParcelableVersion;
|
||||
private int mParcelableSize;
|
||||
private int mStartPosition;
|
||||
private int mSizePosition;
|
||||
private boolean mCreation = false;
|
||||
|
||||
ParcelInfo(Parcel parcel) {
|
||||
mCreation = false;
|
||||
mParcel = parcel;
|
||||
mParcelableVersion = parcel.readInt();
|
||||
mParcelableSize = parcel.readInt();
|
||||
mStartPosition = parcel.dataPosition();
|
||||
}
|
||||
|
||||
ParcelInfo(Parcel parcel, int parcelableVersion) {
|
||||
mCreation = true;
|
||||
mParcel = parcel;
|
||||
mParcelableVersion = parcelableVersion;
|
||||
|
||||
// Write parcelable version, make sure to define explicit changes
|
||||
// within {@link #PARCELABLE_VERSION);
|
||||
mParcel.writeInt(mParcelableVersion);
|
||||
|
||||
// Inject a placeholder that will store the parcel size from this point on
|
||||
// (not including the size itself).
|
||||
mSizePosition = parcel.dataPosition();
|
||||
mParcel.writeInt(0);
|
||||
mStartPosition = parcel.dataPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parcel version from the {@link Parcel} received by the Concierge.
|
||||
* @return {@link #PARCELABLE_VERSION} of the {@link Parcel}
|
||||
*/
|
||||
public int getParcelVersion() {
|
||||
return mParcelableVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the {@link ParcelInfo} for the Concierge.
|
||||
*/
|
||||
public void complete() {
|
||||
if (mCreation) {
|
||||
// Go back and write size
|
||||
mParcelableSize = mParcel.dataPosition() - mStartPosition;
|
||||
mParcel.setDataPosition(mSizePosition);
|
||||
mParcel.writeInt(mParcelableSize);
|
||||
mParcel.setDataPosition(mStartPosition + mParcelableSize);
|
||||
} else {
|
||||
mParcel.setDataPosition(mStartPosition + mParcelableSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
245
app/src/main/java/lineageos/providers/WeatherContract.java
Normal file
245
app/src/main/java/lineageos/providers/WeatherContract.java
Normal file
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.providers;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* The contract between the weather provider and applications.
|
||||
*/
|
||||
public class WeatherContract {
|
||||
|
||||
/**
|
||||
* The authority of the weather content provider
|
||||
*/
|
||||
public static final String AUTHORITY = "org.lineageos.weather";
|
||||
|
||||
/**
|
||||
* A content:// style uri to the authority for the weather provider
|
||||
*/
|
||||
public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
|
||||
|
||||
public static class WeatherColumns {
|
||||
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "weather");
|
||||
|
||||
public static final Uri CURRENT_AND_FORECAST_WEATHER_URI
|
||||
= Uri.withAppendedPath(CONTENT_URI, "current_and_forecast");
|
||||
public static final Uri CURRENT_WEATHER_URI
|
||||
= Uri.withAppendedPath(CONTENT_URI, "current");
|
||||
public static final Uri FORECAST_WEATHER_URI
|
||||
= Uri.withAppendedPath(CONTENT_URI, "forecast");
|
||||
|
||||
/**
|
||||
* The city name
|
||||
* <P>Type: TEXT</P>
|
||||
*/
|
||||
public static final String CURRENT_CITY = "city";
|
||||
|
||||
/**
|
||||
* A Valid {@link WeatherCode}
|
||||
* <P>Type: INTEGER</P>
|
||||
*/
|
||||
public static final String CURRENT_CONDITION_CODE = "condition_code";
|
||||
|
||||
|
||||
/**
|
||||
* A localized string mapped to the current weather condition code. Note that, if no
|
||||
* locale is found, the string will be in english
|
||||
* <P>Type: TEXT</P>
|
||||
*/
|
||||
public static final String CURRENT_CONDITION = "condition";
|
||||
|
||||
/**
|
||||
* The current weather temperature
|
||||
* <P>Type: DOUBLE</P>
|
||||
*/
|
||||
public static final String CURRENT_TEMPERATURE = "temperature";
|
||||
|
||||
/**
|
||||
* The unit in which current temperature is reported
|
||||
* <P>Type: INTEGER</P>
|
||||
* Can be one of the following:
|
||||
* <ul>
|
||||
* <li>{@link TempUnit#CELSIUS}</li>
|
||||
* <li>{@link TempUnit#FAHRENHEIT}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static final String CURRENT_TEMPERATURE_UNIT = "temperature_unit";
|
||||
|
||||
/**
|
||||
* The current weather humidity
|
||||
* <P>Type: DOUBLE</P>
|
||||
*/
|
||||
public static final String CURRENT_HUMIDITY = "humidity";
|
||||
|
||||
/**
|
||||
* The current wind direction (in degrees)
|
||||
* <P>Type: DOUBLE</P>
|
||||
*/
|
||||
public static final String CURRENT_WIND_DIRECTION = "wind_direction";
|
||||
|
||||
/**
|
||||
* The current wind speed
|
||||
* <P>Type: DOUBLE</P>
|
||||
*/
|
||||
public static final String CURRENT_WIND_SPEED = "wind_speed";
|
||||
|
||||
/**
|
||||
* The unit in which the wind speed is reported
|
||||
* <P>Type: INTEGER</P>
|
||||
* Can be one of the following:
|
||||
* <ul>
|
||||
* <li>{@link WindSpeedUnit#KPH}</li>
|
||||
* <li>{@link WindSpeedUnit#MPH}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static final String CURRENT_WIND_SPEED_UNIT = "wind_speed_unit";
|
||||
|
||||
/**
|
||||
* The timestamp when this weather was reported
|
||||
* <P>Type: LONG</P>
|
||||
*/
|
||||
public static final String CURRENT_TIMESTAMP = "timestamp";
|
||||
|
||||
/**
|
||||
* Today's high temperature.
|
||||
* <p>Type: DOUBLE</p>
|
||||
*/
|
||||
public static final String TODAYS_HIGH_TEMPERATURE = "todays_high";
|
||||
|
||||
/**
|
||||
* Today's low temperature.
|
||||
* <p>Type: DOUBLE</p>
|
||||
*/
|
||||
public static final String TODAYS_LOW_TEMPERATURE = "todays_low";
|
||||
|
||||
/**
|
||||
* The forecasted low temperature
|
||||
* <P>Type: DOUBLE</P>
|
||||
*/
|
||||
public static final String FORECAST_LOW = "forecast_low";
|
||||
|
||||
/**
|
||||
* The forecasted high temperature
|
||||
* <P>Type: DOUBLE</P>
|
||||
*/
|
||||
public static final String FORECAST_HIGH = "forecast_high";
|
||||
|
||||
/**
|
||||
* A localized string mapped to the forecasted weather condition code. Note that, if no
|
||||
* locale is found, the string will be in english
|
||||
* <P>Type: TEXT</P>
|
||||
*/
|
||||
public static final String FORECAST_CONDITION = "forecast_condition";
|
||||
|
||||
/**
|
||||
* The code identifying the forecasted weather condition.
|
||||
* @see #CURRENT_CONDITION_CODE
|
||||
*/
|
||||
public static final String FORECAST_CONDITION_CODE = "forecast_condition_code";
|
||||
|
||||
/**
|
||||
* Temperature units
|
||||
*/
|
||||
public static final class TempUnit {
|
||||
private TempUnit() {}
|
||||
public final static int CELSIUS = 1;
|
||||
public final static int FAHRENHEIT = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wind speed units
|
||||
*/
|
||||
public static final class WindSpeedUnit {
|
||||
private WindSpeedUnit() {}
|
||||
/**
|
||||
* Kilometers per hour
|
||||
*/
|
||||
public final static int KPH = 1;
|
||||
|
||||
/**
|
||||
* Miles per hour
|
||||
*/
|
||||
public final static int MPH = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Weather condition codes
|
||||
*/
|
||||
public static final class WeatherCode {
|
||||
private WeatherCode() {}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public final static int WEATHER_CODE_MIN = 0;
|
||||
|
||||
public final static int TORNADO = 0;
|
||||
public final static int TROPICAL_STORM = 1;
|
||||
public final static int HURRICANE = 2;
|
||||
public final static int SEVERE_THUNDERSTORMS = 3;
|
||||
public final static int THUNDERSTORMS = 4;
|
||||
public final static int MIXED_RAIN_AND_SNOW = 5;
|
||||
public final static int MIXED_RAIN_AND_SLEET = 6;
|
||||
public final static int MIXED_SNOW_AND_SLEET = 7;
|
||||
public final static int FREEZING_DRIZZLE = 8;
|
||||
public final static int DRIZZLE = 9;
|
||||
public final static int FREEZING_RAIN = 10;
|
||||
public final static int SHOWERS = 11;
|
||||
public final static int SNOW_FLURRIES = 12;
|
||||
public final static int LIGHT_SNOW_SHOWERS = 13;
|
||||
public final static int BLOWING_SNOW = 14;
|
||||
public final static int SNOW = 15;
|
||||
public final static int HAIL = 16;
|
||||
public final static int SLEET = 17;
|
||||
public final static int DUST = 18;
|
||||
public final static int FOGGY = 19;
|
||||
public final static int HAZE = 20;
|
||||
public final static int SMOKY = 21;
|
||||
public final static int BLUSTERY = 22;
|
||||
public final static int WINDY = 23;
|
||||
public final static int COLD = 24;
|
||||
public final static int CLOUDY = 25;
|
||||
public final static int MOSTLY_CLOUDY_NIGHT = 26;
|
||||
public final static int MOSTLY_CLOUDY_DAY = 27;
|
||||
public final static int PARTLY_CLOUDY_NIGHT = 28;
|
||||
public final static int PARTLY_CLOUDY_DAY = 29;
|
||||
public final static int CLEAR_NIGHT = 30;
|
||||
public final static int SUNNY = 31;
|
||||
public final static int FAIR_NIGHT = 32;
|
||||
public final static int FAIR_DAY = 33;
|
||||
public final static int MIXED_RAIN_AND_HAIL = 34;
|
||||
public final static int HOT = 35;
|
||||
public final static int ISOLATED_THUNDERSTORMS = 36;
|
||||
public final static int SCATTERED_THUNDERSTORMS = 37;
|
||||
public final static int SCATTERED_SHOWERS = 38;
|
||||
public final static int HEAVY_SNOW = 39;
|
||||
public final static int SCATTERED_SNOW_SHOWERS = 40;
|
||||
public final static int PARTLY_CLOUDY = 41;
|
||||
public final static int THUNDERSHOWER = 42;
|
||||
public final static int SNOW_SHOWERS = 43;
|
||||
public final static int ISOLATED_THUNDERSHOWERS = 44;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public final static int WEATHER_CODE_MAX = 44;
|
||||
|
||||
public final static int NOT_AVAILABLE = 3200;
|
||||
}
|
||||
}
|
||||
}
|
435
app/src/main/java/lineageos/weather/LineageWeatherManager.java
Normal file
435
app/src/main/java/lineageos/weather/LineageWeatherManager.java
Normal file
@ -0,0 +1,435 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weather;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import lineageos.app.LineageContextConstants;
|
||||
import lineageos.providers.WeatherContract;
|
||||
|
||||
/**
|
||||
* Provides access to the weather services in the device.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public class LineageWeatherManager {
|
||||
|
||||
private static ILineageWeatherManager sWeatherManagerService;
|
||||
private static LineageWeatherManager sInstance;
|
||||
private Context mContext;
|
||||
private Map<RequestInfo,WeatherUpdateRequestListener> mWeatherUpdateRequestListeners
|
||||
= Collections.synchronizedMap(new HashMap<RequestInfo,WeatherUpdateRequestListener>());
|
||||
private Map<RequestInfo,LookupCityRequestListener> mLookupNameRequestListeners
|
||||
= Collections.synchronizedMap(new HashMap<RequestInfo,LookupCityRequestListener>());
|
||||
private Handler mHandler;
|
||||
private Set<WeatherServiceProviderChangeListener> mProviderChangedListeners = new ArraySet<>();
|
||||
|
||||
private static final String TAG = LineageWeatherManager.class.getSimpleName();
|
||||
|
||||
|
||||
/**
|
||||
* The different request statuses
|
||||
*/
|
||||
public static final class RequestStatus {
|
||||
|
||||
private RequestStatus() {}
|
||||
|
||||
/**
|
||||
* Request successfully completed
|
||||
*/
|
||||
public static final int COMPLETED = 1;
|
||||
/**
|
||||
* An error occurred while trying to honor the request
|
||||
*/
|
||||
public static final int FAILED = -1;
|
||||
/**
|
||||
* The request can't be processed at this time
|
||||
*/
|
||||
public static final int SUBMITTED_TOO_SOON = -2;
|
||||
/**
|
||||
* Another request is already in progress
|
||||
*/
|
||||
public static final int ALREADY_IN_PROGRESS = -3;
|
||||
/**
|
||||
* No match found for the query
|
||||
*/
|
||||
public static final int NO_MATCH_FOUND = -4;
|
||||
}
|
||||
|
||||
private LineageWeatherManager(Context context) {
|
||||
Context appContext = context.getApplicationContext();
|
||||
mContext = (appContext != null) ? appContext : context;
|
||||
sWeatherManagerService = getService();
|
||||
|
||||
if (context.getPackageManager().hasSystemFeature(
|
||||
LineageContextConstants.Features.WEATHER_SERVICES) && (sWeatherManagerService == null)) {
|
||||
Log.wtf(TAG, "Unable to bind the LineageWeatherManagerService");
|
||||
}
|
||||
mHandler = new Handler(appContext.getMainLooper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates an instance of the {@link lineageos.weather.LineageWeatherManager}
|
||||
* @param context
|
||||
* @return {@link LineageWeatherManager}
|
||||
*/
|
||||
public static LineageWeatherManager getInstance(Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new LineageWeatherManager(context);
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@SuppressLint("PrivateApi")
|
||||
public static ILineageWeatherManager getService() {
|
||||
if (sWeatherManagerService != null) {
|
||||
return sWeatherManagerService;
|
||||
}
|
||||
|
||||
// This is a Gadgetbridge hack
|
||||
IBinder binder = null;
|
||||
try {
|
||||
Class localClass = Class.forName("android.os.ServiceManager");
|
||||
Method getService = localClass.getMethod("getService", String.class);
|
||||
Object result = getService.invoke(localClass, LineageContextConstants.LINEAGE_WEATHER_SERVICE);
|
||||
if (result != null) {
|
||||
binder = (IBinder) result;
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (binder != null) {
|
||||
sWeatherManagerService = ILineageWeatherManager.Stub.asInterface(binder);
|
||||
return sWeatherManagerService;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the weather service to request the latest available weather information for
|
||||
* the supplied {@link android.location.Location} location.
|
||||
*
|
||||
* @param location The location you want to get the latest weather data from.
|
||||
* @param listener {@link WeatherUpdateRequestListener} To be notified once the active weather
|
||||
* service provider has finished
|
||||
* processing your request
|
||||
* @return An integer that identifies the request submitted to the weather service
|
||||
* Note that this method might return -1 if an error occurred while trying to submit
|
||||
* the request.
|
||||
*/
|
||||
public int requestWeatherUpdate(@NonNull Location location,
|
||||
@NonNull WeatherUpdateRequestListener listener) {
|
||||
if (sWeatherManagerService == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
int tempUnit = WeatherContract.WeatherColumns.TempUnit.CELSIUS;
|
||||
|
||||
RequestInfo info = new RequestInfo
|
||||
.Builder(mRequestInfoListener)
|
||||
.setLocation(location)
|
||||
.setTemperatureUnit(tempUnit)
|
||||
.build();
|
||||
if (listener != null) mWeatherUpdateRequestListeners.put(info, listener);
|
||||
sWeatherManagerService.updateWeather(info);
|
||||
return info.hashCode();
|
||||
} catch (RemoteException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the weather service to request the latest weather information for the provided
|
||||
* WeatherLocation. This is the preferred method for requesting a weather update.
|
||||
*
|
||||
* @param weatherLocation A {@link lineageos.weather.WeatherLocation} that was previously
|
||||
* obtained by calling
|
||||
* {@link #lookupCity(String, LookupCityRequestListener)}
|
||||
* @param listener {@link WeatherUpdateRequestListener} To be notified once the active weather
|
||||
* service provider has finished
|
||||
* processing your request
|
||||
* @return An integer that identifies the request submitted to the weather service.
|
||||
* Note that this method might return -1 if an error occurred while trying to submit
|
||||
* the request.
|
||||
*/
|
||||
public int requestWeatherUpdate(@NonNull WeatherLocation weatherLocation,
|
||||
@NonNull WeatherUpdateRequestListener listener) {
|
||||
if (sWeatherManagerService == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
int tempUnit = WeatherContract.WeatherColumns.TempUnit.CELSIUS;
|
||||
|
||||
RequestInfo info = new RequestInfo
|
||||
.Builder(mRequestInfoListener)
|
||||
.setWeatherLocation(weatherLocation)
|
||||
.setTemperatureUnit(tempUnit)
|
||||
.build();
|
||||
if (listener != null) mWeatherUpdateRequestListeners.put(info, listener);
|
||||
sWeatherManagerService.updateWeather(info);
|
||||
return info.hashCode();
|
||||
} catch (RemoteException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the active weather provider service to lookup the supplied city name.
|
||||
*
|
||||
* @param city The city name
|
||||
* @param listener {@link LookupCityRequestListener} To be notified once the request has been
|
||||
* completed. Upon success, a list of
|
||||
* {@link lineageos.weather.WeatherLocation}
|
||||
* will be provided
|
||||
* @return An integer that identifies the request submitted to the weather service.
|
||||
* Note that this method might return -1 if an error occurred while trying to submit
|
||||
* the request.
|
||||
*/
|
||||
public int lookupCity(@NonNull String city, @NonNull LookupCityRequestListener listener) {
|
||||
if (sWeatherManagerService == null) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
RequestInfo info = new RequestInfo
|
||||
.Builder(mRequestInfoListener)
|
||||
.setCityName(city)
|
||||
.build();
|
||||
if (listener != null) mLookupNameRequestListeners.put(info, listener);
|
||||
sWeatherManagerService.lookupCity(info);
|
||||
return info.hashCode();
|
||||
} catch (RemoteException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a request that was previously submitted to the weather service.
|
||||
* @param requestId The ID that you received when the request was submitted
|
||||
*/
|
||||
public void cancelRequest(int requestId) {
|
||||
if (sWeatherManagerService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
sWeatherManagerService.cancelRequest(requestId);
|
||||
}catch (RemoteException e){
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link WeatherServiceProviderChangeListener} to be notified when a new weather
|
||||
* service provider becomes active.
|
||||
* @param listener {@link WeatherServiceProviderChangeListener} to register
|
||||
*/
|
||||
public void registerWeatherServiceProviderChangeListener(
|
||||
@NonNull WeatherServiceProviderChangeListener listener) {
|
||||
if (sWeatherManagerService == null) return;
|
||||
|
||||
synchronized (mProviderChangedListeners) {
|
||||
if (mProviderChangedListeners.contains(listener)) {
|
||||
throw new IllegalArgumentException("Listener already registered");
|
||||
}
|
||||
if (mProviderChangedListeners.size() == 0) {
|
||||
try {
|
||||
sWeatherManagerService.registerWeatherServiceProviderChangeListener(
|
||||
mProviderChangeListener);
|
||||
} catch (RemoteException e){
|
||||
}
|
||||
}
|
||||
mProviderChangedListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a listener
|
||||
* @param listener A previously registered {@link WeatherServiceProviderChangeListener}
|
||||
*/
|
||||
public void unregisterWeatherServiceProviderChangeListener(
|
||||
@NonNull WeatherServiceProviderChangeListener listener) {
|
||||
if (sWeatherManagerService == null) return;
|
||||
|
||||
synchronized (mProviderChangedListeners) {
|
||||
if (!mProviderChangedListeners.contains(listener)) {
|
||||
throw new IllegalArgumentException("Listener was never registered");
|
||||
}
|
||||
mProviderChangedListeners.remove(listener);
|
||||
if (mProviderChangedListeners.size() == 0) {
|
||||
try {
|
||||
sWeatherManagerService.unregisterWeatherServiceProviderChangeListener(
|
||||
mProviderChangeListener);
|
||||
} catch(RemoteException e){
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the service's label as declared by the active weather service provider in its manifest
|
||||
* @return the service's label
|
||||
*/
|
||||
public String getActiveWeatherServiceProviderLabel() {
|
||||
if (sWeatherManagerService == null) return null;
|
||||
|
||||
try {
|
||||
return sWeatherManagerService.getActiveWeatherServiceProviderLabel();
|
||||
} catch(RemoteException e){
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final IWeatherServiceProviderChangeListener mProviderChangeListener =
|
||||
new IWeatherServiceProviderChangeListener.Stub() {
|
||||
@Override
|
||||
public void onWeatherServiceProviderChanged(final String providerName) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (mProviderChangedListeners) {
|
||||
List<WeatherServiceProviderChangeListener> deadListeners
|
||||
= new ArrayList<>();
|
||||
for (WeatherServiceProviderChangeListener listener
|
||||
: mProviderChangedListeners) {
|
||||
try {
|
||||
listener.onWeatherServiceProviderChanged(providerName);
|
||||
} catch (Throwable e) {
|
||||
deadListeners.add(listener);
|
||||
}
|
||||
}
|
||||
if (deadListeners.size() > 0) {
|
||||
for (WeatherServiceProviderChangeListener listener : deadListeners) {
|
||||
mProviderChangedListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private final IRequestInfoListener mRequestInfoListener = new IRequestInfoListener.Stub() {
|
||||
|
||||
@Override
|
||||
public void onWeatherRequestCompleted(final RequestInfo requestInfo, final int status,
|
||||
final WeatherInfo weatherInfo) {
|
||||
final WeatherUpdateRequestListener listener
|
||||
= mWeatherUpdateRequestListeners.remove(requestInfo);
|
||||
if (listener != null) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onWeatherRequestCompleted(status, weatherInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLookupCityRequestCompleted(RequestInfo requestInfo, final int status,
|
||||
final List<WeatherLocation> weatherLocations) {
|
||||
|
||||
final LookupCityRequestListener listener
|
||||
= mLookupNameRequestListeners.remove(requestInfo);
|
||||
if (listener != null) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onLookupCityRequestCompleted(status, weatherLocations);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface used to receive notifications upon completion of a weather update request
|
||||
*/
|
||||
public interface WeatherUpdateRequestListener {
|
||||
/**
|
||||
* This method will be called when the weather service provider has finished processing the
|
||||
* request
|
||||
*
|
||||
* @param status See {@link RequestStatus}
|
||||
*
|
||||
* @param weatherInfo A fully populated {@link WeatherInfo} if state is
|
||||
* {@link RequestStatus#COMPLETED}, null otherwise
|
||||
*/
|
||||
void onWeatherRequestCompleted(int status, WeatherInfo weatherInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface used to receive notifications upon completion of a request to lookup a city name
|
||||
*/
|
||||
public interface LookupCityRequestListener {
|
||||
/**
|
||||
* This method will be called when the weather service provider has finished processing the
|
||||
* request.
|
||||
*
|
||||
* @param status See {@link RequestStatus}
|
||||
*
|
||||
* @param locations A list of {@link WeatherLocation} if the status is
|
||||
* {@link RequestStatus#COMPLETED}, null otherwise
|
||||
*/
|
||||
void onLookupCityRequestCompleted(int status, List<WeatherLocation> locations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface used to be notified when the user changes the weather service provider
|
||||
*/
|
||||
public interface WeatherServiceProviderChangeListener {
|
||||
/**
|
||||
* This method will be called when a new weather service provider becomes active in the
|
||||
* system. The parameter can be null when
|
||||
* <p>The user removed the active weather service provider from the system </p>
|
||||
* <p>The active weather provider was disabled.</p>
|
||||
*
|
||||
* @param providerLabel The label as declared on the weather service provider manifest
|
||||
*/
|
||||
void onWeatherServiceProviderChanged(String providerLabel);
|
||||
}
|
||||
}
|
379
app/src/main/java/lineageos/weather/RequestInfo.java
Normal file
379
app/src/main/java/lineageos/weather/RequestInfo.java
Normal file
@ -0,0 +1,379 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weather;
|
||||
|
||||
import android.location.Location;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import lineageos.os.Build;
|
||||
import lineageos.os.Concierge;
|
||||
import lineageos.os.Concierge.ParcelInfo;
|
||||
import lineageos.providers.WeatherContract;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This class holds the information of a request submitted to the active weather provider service
|
||||
*/
|
||||
public final class RequestInfo implements Parcelable {
|
||||
|
||||
private Location mLocation;
|
||||
private String mCityName;
|
||||
private WeatherLocation mWeatherLocation;
|
||||
private int mRequestType;
|
||||
private IRequestInfoListener mListener;
|
||||
private int mTempUnit;
|
||||
private String mKey;
|
||||
private boolean mIsQueryOnly;
|
||||
|
||||
/**
|
||||
* A request to update the weather data using a geographical {@link android.location.Location}
|
||||
*/
|
||||
public static final int TYPE_WEATHER_BY_GEO_LOCATION_REQ = 1;
|
||||
/**
|
||||
* A request to update the weather data using a {@link WeatherLocation}
|
||||
*/
|
||||
public static final int TYPE_WEATHER_BY_WEATHER_LOCATION_REQ = 2;
|
||||
|
||||
/**
|
||||
* A request to look up a city name
|
||||
*/
|
||||
public static final int TYPE_LOOKUP_CITY_NAME_REQ = 3;
|
||||
|
||||
private RequestInfo() {}
|
||||
|
||||
/* package */ static class Builder {
|
||||
private Location mLocation;
|
||||
private String mCityName;
|
||||
private WeatherLocation mWeatherLocation;
|
||||
private int mRequestType;
|
||||
private IRequestInfoListener mListener;
|
||||
private int mTempUnit = WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT;
|
||||
private boolean mIsQueryOnly = false;
|
||||
|
||||
public Builder(IRequestInfoListener listener) {
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the city name and identifies this request as a {@link #TYPE_LOOKUP_CITY_NAME_REQ}
|
||||
* request. If set, will null out the location and weather location. Attempting to set
|
||||
* a null city name will get you an IllegalArgumentException
|
||||
*/
|
||||
public Builder setCityName(String cityName) {
|
||||
if (cityName == null) {
|
||||
throw new IllegalArgumentException("City name can't be null");
|
||||
}
|
||||
this.mCityName = cityName;
|
||||
this.mRequestType = TYPE_LOOKUP_CITY_NAME_REQ;
|
||||
this.mLocation = null;
|
||||
this.mWeatherLocation = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Location and identifies this request as a
|
||||
* {@link #TYPE_WEATHER_BY_GEO_LOCATION_REQ}. If set, will null out the city name and
|
||||
* weather location. Attempting to set a null location will get you an
|
||||
* IllegalArgumentException
|
||||
*/
|
||||
public Builder setLocation(Location location) {
|
||||
if (location == null) {
|
||||
throw new IllegalArgumentException("Location can't be null");
|
||||
}
|
||||
this.mLocation = new Location(location);
|
||||
this.mCityName = null;
|
||||
this.mWeatherLocation = null;
|
||||
this.mRequestType = TYPE_WEATHER_BY_GEO_LOCATION_REQ;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the weather location and identifies this request as a
|
||||
* {@link #TYPE_WEATHER_BY_WEATHER_LOCATION_REQ}. If set, will null out the location and
|
||||
* city name. Attempting to set a null weather location will get you an
|
||||
* IllegalArgumentException
|
||||
*/
|
||||
public Builder setWeatherLocation(WeatherLocation weatherLocation) {
|
||||
if (weatherLocation == null) {
|
||||
throw new IllegalArgumentException("WeatherLocation can't be null");
|
||||
}
|
||||
this.mWeatherLocation = weatherLocation;
|
||||
this.mLocation = null;
|
||||
this.mCityName = null;
|
||||
this.mRequestType = TYPE_WEATHER_BY_WEATHER_LOCATION_REQ;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the unit in which the temperature will be reported if the request is honored.
|
||||
* Valid values are:
|
||||
* <ul>
|
||||
* {@link lineageos.providers.WeatherContract.WeatherColumns.TempUnit#CELSIUS}
|
||||
* {@link lineageos.providers.WeatherContract.WeatherColumns.TempUnit#FAHRENHEIT}
|
||||
* </ul>
|
||||
* Any other value will generate an IllegalArgumentException. If the temperature unit is not
|
||||
* set, the default will be degrees Fahrenheit
|
||||
* @param unit A valid temperature unit
|
||||
*/
|
||||
public Builder setTemperatureUnit(int unit) {
|
||||
if (!isValidTempUnit(unit)) {
|
||||
throw new IllegalArgumentException("Invalid temperature unit");
|
||||
}
|
||||
this.mTempUnit = unit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is a weather request, marks the request as a query only, meaning that the
|
||||
* content provider won't be updated after the active weather service has finished
|
||||
* processing the request.
|
||||
*/
|
||||
public Builder queryOnly() {
|
||||
switch (mRequestType) {
|
||||
case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||
case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||
this.mIsQueryOnly = true;
|
||||
break;
|
||||
default:
|
||||
this.mIsQueryOnly = false;
|
||||
break;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine all of the options that have been set and return a new {@link RequestInfo} object
|
||||
* @return {@link RequestInfo}
|
||||
*/
|
||||
public RequestInfo build() {
|
||||
RequestInfo info = new RequestInfo();
|
||||
info.mListener = this.mListener;
|
||||
info.mRequestType = this.mRequestType;
|
||||
info.mCityName = this.mCityName;
|
||||
info.mWeatherLocation = this.mWeatherLocation;
|
||||
info.mLocation = this.mLocation;
|
||||
info.mTempUnit = this.mTempUnit;
|
||||
info.mIsQueryOnly = this.mIsQueryOnly;
|
||||
info.mKey = UUID.randomUUID().toString();
|
||||
return info;
|
||||
}
|
||||
|
||||
private boolean isValidTempUnit(int unit) {
|
||||
switch (unit) {
|
||||
case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
|
||||
case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private RequestInfo(Parcel parcel) {
|
||||
// Read parcelable version via the Concierge
|
||||
ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
|
||||
int parcelableVersion = parcelInfo.getParcelVersion();
|
||||
|
||||
if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
|
||||
mKey = parcel.readString();
|
||||
mRequestType = parcel.readInt();
|
||||
switch (mRequestType) {
|
||||
case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||
mLocation = Location.CREATOR.createFromParcel(parcel);
|
||||
mTempUnit = parcel.readInt();
|
||||
break;
|
||||
case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||
mWeatherLocation = WeatherLocation.CREATOR.createFromParcel(parcel);
|
||||
mTempUnit = parcel.readInt();
|
||||
break;
|
||||
case TYPE_LOOKUP_CITY_NAME_REQ:
|
||||
mCityName = parcel.readString();
|
||||
break;
|
||||
}
|
||||
mIsQueryOnly = (parcel.readInt() == 1);
|
||||
mListener = IRequestInfoListener.Stub.asInterface(parcel.readStrongBinder());
|
||||
}
|
||||
|
||||
// Complete parcel info for the concierge
|
||||
parcelInfo.complete();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return The request type
|
||||
*/
|
||||
public int getRequestType() {
|
||||
return mRequestType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link android.location.Location} if this is a request by location, null
|
||||
* otherwise
|
||||
*/
|
||||
public Location getLocation() {
|
||||
return new Location(mLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link lineageos.weather.WeatherLocation} if this is a request by weather
|
||||
* location, null otherwise
|
||||
*/
|
||||
public WeatherLocation getWeatherLocation() {
|
||||
return mWeatherLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public IRequestInfoListener getRequestListener() {
|
||||
return mListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the city name if this is a lookup request, null otherwise
|
||||
*/
|
||||
public String getCityName() {
|
||||
return mCityName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the temperature unit if this is a weather request, -1 otherwise
|
||||
*/
|
||||
public int getTemperatureUnit() {
|
||||
switch (mRequestType) {
|
||||
case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||
case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||
return mTempUnit;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if this is a weather request, whether the request will update the content provider.
|
||||
* False for other kind of requests
|
||||
* @hide
|
||||
*/
|
||||
public boolean isQueryOnlyWeatherRequest() {
|
||||
switch (mRequestType) {
|
||||
case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||
case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||
return mIsQueryOnly;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static final Creator<RequestInfo> CREATOR = new Creator<RequestInfo>() {
|
||||
@Override
|
||||
public RequestInfo createFromParcel(Parcel in) {
|
||||
return new RequestInfo(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestInfo[] newArray(int size) {
|
||||
return new RequestInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
// Tell the concierge to prepare the parcel
|
||||
ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
|
||||
|
||||
// ==== ELDERBERRY =====
|
||||
dest.writeString(mKey);
|
||||
dest.writeInt(mRequestType);
|
||||
switch (mRequestType) {
|
||||
case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||
mLocation.writeToParcel(dest, 0);
|
||||
dest.writeInt(mTempUnit);
|
||||
break;
|
||||
case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||
mWeatherLocation.writeToParcel(dest, 0);
|
||||
dest.writeInt(mTempUnit);
|
||||
break;
|
||||
case TYPE_LOOKUP_CITY_NAME_REQ:
|
||||
dest.writeString(mCityName);
|
||||
break;
|
||||
}
|
||||
dest.writeInt(mIsQueryOnly == true ? 1 : 0);
|
||||
dest.writeStrongBinder(mListener.asBinder());
|
||||
|
||||
// Complete parcel info for the concierge
|
||||
parcelInfo.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("{ Request for ");
|
||||
switch (mRequestType) {
|
||||
case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||
builder.append("Location: ").append(mLocation);
|
||||
builder.append(" Temp Unit: ");
|
||||
if (mTempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
|
||||
builder.append("Fahrenheit");
|
||||
} else {
|
||||
builder.append(" Celsius");
|
||||
}
|
||||
break;
|
||||
case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||
builder.append("WeatherLocation: ").append(mWeatherLocation);
|
||||
builder.append(" Temp Unit: ");
|
||||
if (mTempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
|
||||
builder.append("Fahrenheit");
|
||||
} else {
|
||||
builder.append(" Celsius");
|
||||
}
|
||||
break;
|
||||
case TYPE_LOOKUP_CITY_NAME_REQ:
|
||||
builder.append("Lookup City: ").append(mCityName);
|
||||
break;
|
||||
}
|
||||
return builder.append(" }").toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
|
||||
if (getClass() == obj.getClass()) {
|
||||
RequestInfo info = (RequestInfo) obj;
|
||||
return (TextUtils.equals(mKey, info.mKey));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
642
app/src/main/java/lineageos/weather/WeatherInfo.java
Executable file
642
app/src/main/java/lineageos/weather/WeatherInfo.java
Executable file
@ -0,0 +1,642 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanongenMod 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 lineageos.weather;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import lineageos.os.Build;
|
||||
import lineageos.os.Concierge;
|
||||
import lineageos.os.Concierge.ParcelInfo;
|
||||
import lineageos.providers.WeatherContract;
|
||||
import lineageos.weatherservice.ServiceRequest;
|
||||
import lineageos.weatherservice.ServiceRequestResult;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This class represents the weather information that a
|
||||
* {@link lineageos.weatherservice.WeatherProviderService} will use to update the weather content
|
||||
* provider. A weather provider service will be called by the system to process an update
|
||||
* request at any time. If the service successfully processes the request, then the weather provider
|
||||
* service is responsible of calling
|
||||
* {@link ServiceRequest#complete(ServiceRequestResult)} to notify the
|
||||
* system that the request was completed and that the weather content provider should be updated
|
||||
* with the supplied weather information.
|
||||
*/
|
||||
public final class WeatherInfo implements Parcelable {
|
||||
|
||||
private String mCity;
|
||||
private int mConditionCode;
|
||||
private double mTemperature;
|
||||
private int mTempUnit;
|
||||
private double mTodaysHighTemp;
|
||||
private double mTodaysLowTemp;
|
||||
private double mHumidity;
|
||||
private double mWindSpeed;
|
||||
private double mWindDirection;
|
||||
private int mWindSpeedUnit;
|
||||
private long mTimestamp;
|
||||
private List<DayForecast> mForecastList;
|
||||
private String mKey;
|
||||
|
||||
private WeatherInfo() {}
|
||||
|
||||
/**
|
||||
* Builder class for {@link WeatherInfo}
|
||||
*/
|
||||
public static class Builder {
|
||||
private String mCity;
|
||||
private int mConditionCode = WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE;
|
||||
private double mTemperature;
|
||||
private int mTempUnit;
|
||||
private double mTodaysHighTemp = Double.NaN;
|
||||
private double mTodaysLowTemp = Double.NaN;
|
||||
private double mHumidity = Double.NaN;
|
||||
private double mWindSpeed = Double.NaN;
|
||||
private double mWindDirection = Double.NaN;
|
||||
private int mWindSpeedUnit = WeatherContract.WeatherColumns.WindSpeedUnit.MPH;
|
||||
private long mTimestamp = -1;
|
||||
private List<DayForecast> mForecastList = new ArrayList<>(0);
|
||||
|
||||
/**
|
||||
* @param cityName A valid city name. Attempting to pass null will get you an
|
||||
* IllegalArgumentException
|
||||
* @param temperature A valid temperature value. Attempting pass an invalid double value,
|
||||
* will get you an IllegalArgumentException
|
||||
* @param tempUnit A valid temperature unit value. See
|
||||
* {@link lineageos.providers.WeatherContract.WeatherColumns.TempUnit} for
|
||||
* valid values. Attempting to pass an invalid temperature unit will get you
|
||||
* an IllegalArgumentException
|
||||
*/
|
||||
public Builder(@NonNull String cityName, double temperature, int tempUnit) {
|
||||
if (cityName == null) {
|
||||
throw new IllegalArgumentException("City name can't be null");
|
||||
}
|
||||
if (Double.isNaN(temperature)) {
|
||||
throw new IllegalArgumentException("Invalid temperature");
|
||||
}
|
||||
if (!isValidTempUnit(tempUnit)) {
|
||||
throw new IllegalArgumentException("Invalid temperature unit");
|
||||
}
|
||||
this.mCity = cityName;
|
||||
this.mTemperature = temperature;
|
||||
this.mTempUnit = tempUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timeStamp A timestamp indicating when this data was generated. If timestamps is
|
||||
* not set, then the builder will set it to the time of object creation
|
||||
* @return The {@link Builder} instance
|
||||
*/
|
||||
public Builder setTimestamp(long timeStamp) {
|
||||
mTimestamp = timeStamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param humidity The weather humidity. Attempting to pass an invalid double value will get
|
||||
* you an IllegalArgumentException
|
||||
* @return The {@link Builder} instance
|
||||
*/
|
||||
public Builder setHumidity(double humidity) {
|
||||
if (Double.isNaN(humidity)) {
|
||||
throw new IllegalArgumentException("Invalid humidity value");
|
||||
}
|
||||
|
||||
mHumidity = humidity;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param windSpeed The wind speed. Attempting to pass an invalid double value will get you
|
||||
* an IllegalArgumentException
|
||||
* @param windDirection The wind direction. Attempting to pass an invalid double value will
|
||||
* get you an IllegalArgumentException
|
||||
* @param windSpeedUnit A valid wind speed direction unit. See
|
||||
* {@link lineageos.providers.WeatherContract.WeatherColumns.WindSpeedUnit}
|
||||
* for valid values. Attempting to pass an invalid speed unit will get
|
||||
* you an IllegalArgumentException
|
||||
* @return The {@link Builder} instance
|
||||
*/
|
||||
public Builder setWind(double windSpeed, double windDirection, int windSpeedUnit) {
|
||||
if (Double.isNaN(windSpeed)) {
|
||||
throw new IllegalArgumentException("Invalid wind speed value");
|
||||
}
|
||||
if (Double.isNaN(windDirection)) {
|
||||
throw new IllegalArgumentException("Invalid wind direction value");
|
||||
}
|
||||
if (!isValidWindSpeedUnit(windSpeedUnit)) {
|
||||
throw new IllegalArgumentException("Invalid speed unit");
|
||||
}
|
||||
mWindSpeed = windSpeed;
|
||||
mWindSpeedUnit = windSpeedUnit;
|
||||
mWindDirection = windDirection;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param conditionCode A valid weather condition code. See
|
||||
* {@link lineageos.providers.WeatherContract.WeatherColumns.WeatherCode}
|
||||
* for valid codes. Attempting to pass an invalid code will get you an
|
||||
* IllegalArgumentException.
|
||||
* @return The {@link Builder} instance
|
||||
*/
|
||||
public Builder setWeatherCondition(int conditionCode) {
|
||||
if (!isValidWeatherCode(conditionCode)) {
|
||||
throw new IllegalArgumentException("Invalid weather condition code");
|
||||
}
|
||||
mConditionCode = conditionCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param forecasts A valid array list of {@link DayForecast} objects. Attempting to pass
|
||||
* null will get you an IllegalArgumentException'
|
||||
* @return The {@link Builder} instance
|
||||
*/
|
||||
public Builder setForecast(@NonNull List<DayForecast> forecasts) {
|
||||
if (forecasts == null) {
|
||||
throw new IllegalArgumentException("Forecast list can't be null");
|
||||
}
|
||||
mForecastList = forecasts;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param todaysHigh Today's high temperature. Attempting to pass an invalid double value
|
||||
* will get you an IllegalArgumentException
|
||||
* @return The {@link Builder} instance
|
||||
*/
|
||||
public Builder setTodaysHigh(double todaysHigh) {
|
||||
if (Double.isNaN(todaysHigh)) {
|
||||
throw new IllegalArgumentException("Invalid temperature value");
|
||||
}
|
||||
mTodaysHighTemp = todaysHigh;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param todaysLow Today's low temperature. Attempting to pass an invalid double value will
|
||||
* get you an IllegalArgumentException
|
||||
* @return
|
||||
*/
|
||||
public Builder setTodaysLow(double todaysLow) {
|
||||
if (Double.isNaN(todaysLow)) {
|
||||
throw new IllegalArgumentException("Invalid temperature value");
|
||||
}
|
||||
mTodaysLowTemp = todaysLow;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine all of the options that have been set and return a new {@link WeatherInfo} object
|
||||
* @return {@link WeatherInfo}
|
||||
*/
|
||||
public WeatherInfo build() {
|
||||
WeatherInfo info = new WeatherInfo();
|
||||
info.mCity = this.mCity;
|
||||
info.mConditionCode = this.mConditionCode;
|
||||
info.mTemperature = this.mTemperature;
|
||||
info.mTempUnit = this.mTempUnit;
|
||||
info.mHumidity = this.mHumidity;
|
||||
info.mWindSpeed = this.mWindSpeed;
|
||||
info.mWindDirection = this.mWindDirection;
|
||||
info.mWindSpeedUnit = this.mWindSpeedUnit;
|
||||
info.mTimestamp = this.mTimestamp == -1 ? System.currentTimeMillis() : this.mTimestamp;
|
||||
info.mForecastList = this.mForecastList;
|
||||
info.mTodaysHighTemp = this.mTodaysHighTemp;
|
||||
info.mTodaysLowTemp = this.mTodaysLowTemp;
|
||||
info.mKey = UUID.randomUUID().toString();
|
||||
return info;
|
||||
}
|
||||
|
||||
private boolean isValidTempUnit(int unit) {
|
||||
switch (unit) {
|
||||
case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
|
||||
case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidWindSpeedUnit(int unit) {
|
||||
switch (unit) {
|
||||
case WeatherContract.WeatherColumns.WindSpeedUnit.KPH:
|
||||
case WeatherContract.WeatherColumns.WindSpeedUnit.MPH:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static boolean isValidWeatherCode(int code) {
|
||||
if (code < WeatherContract.WeatherColumns.WeatherCode.WEATHER_CODE_MIN
|
||||
|| code > WeatherContract.WeatherColumns.WeatherCode.WEATHER_CODE_MAX) {
|
||||
if (code != WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return city name
|
||||
*/
|
||||
public String getCity() {
|
||||
return mCity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An implementation specific weather condition code
|
||||
*/
|
||||
public int getConditionCode() {
|
||||
return mConditionCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return humidity
|
||||
*/
|
||||
public double getHumidity() {
|
||||
return mHumidity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return time stamp when the request was processed
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return wind direction (degrees)
|
||||
*/
|
||||
public double getWindDirection() {
|
||||
return mWindDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return wind speed
|
||||
*/
|
||||
public double getWindSpeed() {
|
||||
return mWindSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return wind speed unit
|
||||
*/
|
||||
public int getWindSpeedUnit() {
|
||||
return mWindSpeedUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return current temperature
|
||||
*/
|
||||
public double getTemperature() {
|
||||
return mTemperature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return temperature unit
|
||||
*/
|
||||
public int getTemperatureUnit() {
|
||||
return mTempUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return today's high temperature
|
||||
*/
|
||||
public double getTodaysHigh() {
|
||||
return mTodaysHighTemp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return today's low temperature
|
||||
*/
|
||||
public double getTodaysLow() {
|
||||
return mTodaysLowTemp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return List of {@link lineageos.weather.WeatherInfo.DayForecast}. This list will contain
|
||||
* the forecast weather for the upcoming days. If you want to know today's high and low
|
||||
* temperatures, use {@link WeatherInfo#getTodaysHigh()} and {@link WeatherInfo#getTodaysLow()}
|
||||
*/
|
||||
public List<DayForecast> getForecasts() {
|
||||
return new ArrayList<>(mForecastList);
|
||||
}
|
||||
|
||||
private WeatherInfo(Parcel parcel) {
|
||||
// Read parcelable version via the Concierge
|
||||
ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
|
||||
int parcelableVersion = parcelInfo.getParcelVersion();
|
||||
|
||||
if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
|
||||
mKey = parcel.readString();
|
||||
mCity = parcel.readString();
|
||||
mConditionCode = parcel.readInt();
|
||||
mTemperature = parcel.readDouble();
|
||||
mTempUnit = parcel.readInt();
|
||||
mHumidity = parcel.readDouble();
|
||||
mWindSpeed = parcel.readDouble();
|
||||
mWindDirection = parcel.readDouble();
|
||||
mWindSpeedUnit = parcel.readInt();
|
||||
mTodaysHighTemp = parcel.readDouble();
|
||||
mTodaysLowTemp = parcel.readDouble();
|
||||
mTimestamp = parcel.readLong();
|
||||
int forecastListSize = parcel.readInt();
|
||||
mForecastList = new ArrayList<>();
|
||||
while (forecastListSize > 0) {
|
||||
mForecastList.add(DayForecast.CREATOR.createFromParcel(parcel));
|
||||
forecastListSize--;
|
||||
}
|
||||
}
|
||||
|
||||
// Complete parcel info for the concierge
|
||||
parcelInfo.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
// Tell the concierge to prepare the parcel
|
||||
ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
|
||||
|
||||
// ==== ELDERBERRY =====
|
||||
dest.writeString(mKey);
|
||||
dest.writeString(mCity);
|
||||
dest.writeInt(mConditionCode);
|
||||
dest.writeDouble(mTemperature);
|
||||
dest.writeInt(mTempUnit);
|
||||
dest.writeDouble(mHumidity);
|
||||
dest.writeDouble(mWindSpeed);
|
||||
dest.writeDouble(mWindDirection);
|
||||
dest.writeInt(mWindSpeedUnit);
|
||||
dest.writeDouble(mTodaysHighTemp);
|
||||
dest.writeDouble(mTodaysLowTemp);
|
||||
dest.writeLong(mTimestamp);
|
||||
dest.writeInt(mForecastList.size());
|
||||
for (DayForecast dayForecast : mForecastList) {
|
||||
dayForecast.writeToParcel(dest, 0);
|
||||
}
|
||||
|
||||
// Complete parcel info for the concierge
|
||||
parcelInfo.complete();
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<WeatherInfo> CREATOR =
|
||||
new Parcelable.Creator<WeatherInfo>() {
|
||||
|
||||
@Override
|
||||
public WeatherInfo createFromParcel(Parcel source) {
|
||||
return new WeatherInfo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeatherInfo[] newArray(int size) {
|
||||
return new WeatherInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This class represents the weather forecast for a given day. Do not add low and high
|
||||
* temperatures for the current day in this list. Use
|
||||
* {@link WeatherInfo.Builder#setTodaysHigh(double)} and
|
||||
* {@link WeatherInfo.Builder#setTodaysLow(double)} instead.
|
||||
*/
|
||||
public static class DayForecast implements Parcelable{
|
||||
double mLow;
|
||||
double mHigh;
|
||||
int mConditionCode;
|
||||
String mKey;
|
||||
|
||||
private DayForecast() {}
|
||||
|
||||
/**
|
||||
* Builder class for {@link DayForecast}
|
||||
*/
|
||||
public static class Builder {
|
||||
double mLow = Double.NaN;
|
||||
double mHigh = Double.NaN;
|
||||
int mConditionCode;
|
||||
|
||||
/**
|
||||
* @param conditionCode A valid weather condition code. See
|
||||
* {@link lineageos.providers.WeatherContract.WeatherColumns.WeatherCode} for valid
|
||||
* values. Attempting to pass an invalid code will get you an
|
||||
* IllegalArgumentException
|
||||
*/
|
||||
public Builder(int conditionCode) {
|
||||
if (!isValidWeatherCode(conditionCode)) {
|
||||
throw new IllegalArgumentException("Invalid weather condition code");
|
||||
}
|
||||
mConditionCode = conditionCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param high Forecast high temperature for this day. Attempting to pass an invalid
|
||||
* double value will get you an IllegalArgumentException
|
||||
* @return The {@link Builder} instance
|
||||
*/
|
||||
public Builder setHigh(double high) {
|
||||
if (Double.isNaN(high)) {
|
||||
throw new IllegalArgumentException("Invalid high forecast temperature");
|
||||
}
|
||||
mHigh = high;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param low Forecast low temperate for this day. Attempting to pass an invalid double
|
||||
* value will get you an IllegalArgumentException
|
||||
* @return The {@link Builder} instance
|
||||
*/
|
||||
public Builder setLow(double low) {
|
||||
if (Double.isNaN(low)) {
|
||||
throw new IllegalArgumentException("Invalid low forecast temperature");
|
||||
}
|
||||
mLow = low;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Combine all of the options that have been set and return a new {@link DayForecast}
|
||||
* object
|
||||
* @return {@link DayForecast}
|
||||
*/
|
||||
public DayForecast build() {
|
||||
DayForecast forecast = new DayForecast();
|
||||
forecast.mLow = this.mLow;
|
||||
forecast.mHigh = this.mHigh;
|
||||
forecast.mConditionCode = this.mConditionCode;
|
||||
forecast.mKey = UUID.randomUUID().toString();
|
||||
return forecast;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return forecasted low temperature
|
||||
*/
|
||||
public double getLow() {
|
||||
return mLow;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return not what you think. Returns the forecasted high temperature
|
||||
*/
|
||||
public double getHigh() {
|
||||
return mHigh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return forecasted weather condition code. Implementation specific
|
||||
*/
|
||||
public int getConditionCode() {
|
||||
return mConditionCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
// Tell the concierge to prepare the parcel
|
||||
ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
|
||||
|
||||
// ==== ELDERBERRY =====
|
||||
dest.writeString(mKey);
|
||||
dest.writeDouble(mLow);
|
||||
dest.writeDouble(mHigh);
|
||||
dest.writeInt(mConditionCode);
|
||||
|
||||
// Complete parcel info for the concierge
|
||||
parcelInfo.complete();
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<DayForecast> CREATOR =
|
||||
new Parcelable.Creator<DayForecast>() {
|
||||
@Override
|
||||
public DayForecast createFromParcel(Parcel source) {
|
||||
return new DayForecast(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DayForecast[] newArray(int size) {
|
||||
return new DayForecast[size];
|
||||
}
|
||||
};
|
||||
|
||||
private DayForecast(Parcel parcel) {
|
||||
// Read parcelable version via the Concierge
|
||||
ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
|
||||
int parcelableVersion = parcelInfo.getParcelVersion();
|
||||
|
||||
if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
|
||||
mKey = parcel.readString();
|
||||
mLow = parcel.readDouble();
|
||||
mHigh = parcel.readDouble();
|
||||
mConditionCode = parcel.readInt();
|
||||
}
|
||||
|
||||
// Complete parcel info for the concierge
|
||||
parcelInfo.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder()
|
||||
.append("{Low temp: ").append(mLow)
|
||||
.append(" High temp: ").append(mHigh)
|
||||
.append(" Condition code: ").append(mConditionCode)
|
||||
.append("}").toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
|
||||
if (getClass() == obj.getClass()) {
|
||||
DayForecast forecast = (DayForecast) obj;
|
||||
return (TextUtils.equals(mKey, forecast.mKey));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder()
|
||||
.append(" City Name: ").append(mCity)
|
||||
.append(" Condition Code: ").append(mConditionCode)
|
||||
.append(" Temperature: ").append(mTemperature)
|
||||
.append(" Temperature Unit: ").append(mTempUnit)
|
||||
.append(" Humidity: ").append(mHumidity)
|
||||
.append(" Wind speed: ").append(mWindSpeed)
|
||||
.append(" Wind direction: ").append(mWindDirection)
|
||||
.append(" Wind Speed Unit: ").append(mWindSpeedUnit)
|
||||
.append(" Today's high temp: ").append(mTodaysHighTemp)
|
||||
.append(" Today's low temp: ").append(mTodaysLowTemp)
|
||||
.append(" Timestamp: ").append(mTimestamp).append(" Forecasts: [");
|
||||
for (DayForecast dayForecast : mForecastList) {
|
||||
builder.append(dayForecast.toString());
|
||||
}
|
||||
return builder.append("]}").toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
|
||||
if (getClass() == obj.getClass()) {
|
||||
WeatherInfo info = (WeatherInfo) obj;
|
||||
return (TextUtils.equals(mKey, info.mKey));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
274
app/src/main/java/lineageos/weather/WeatherLocation.java
Normal file
274
app/src/main/java/lineageos/weather/WeatherLocation.java
Normal file
@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weather;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import lineageos.os.Build;
|
||||
import lineageos.os.Concierge;
|
||||
import lineageos.os.Concierge.ParcelInfo;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A class representing a geographical location that a weather service provider can use to
|
||||
* get weather data from. Each service provider will potentially populate objects of this class
|
||||
* with different content, so make sure you don't preserve the values when a service provider
|
||||
* is changed
|
||||
*/
|
||||
public final class WeatherLocation implements Parcelable{
|
||||
private String mCityId;
|
||||
private String mCity;
|
||||
private String mState;
|
||||
private String mPostal;
|
||||
private String mCountryId;
|
||||
private String mCountry;
|
||||
private String mKey;
|
||||
|
||||
private WeatherLocation() {}
|
||||
|
||||
/**
|
||||
* Builder class for {@link WeatherLocation}
|
||||
*/
|
||||
public static class Builder {
|
||||
String mCityId = "";
|
||||
String mCity = "";
|
||||
String mState = "";
|
||||
String mPostal = "";
|
||||
String mCountryId = "";
|
||||
String mCountry = "";
|
||||
|
||||
/**
|
||||
* @param cityId An identifier for the city (for example WOEID - Where On Earth IDentifier)
|
||||
* @param cityName The name of the city
|
||||
*/
|
||||
public Builder(String cityId, String cityName) {
|
||||
if (cityId == null || cityName == null) {
|
||||
throw new IllegalArgumentException("Illegal to set city id AND city to null");
|
||||
}
|
||||
this.mCityId = cityId;
|
||||
this.mCity = cityName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cityName The name of the city
|
||||
*/
|
||||
public Builder(String cityName) {
|
||||
if (cityName == null) {
|
||||
throw new IllegalArgumentException("City name can't be null");
|
||||
}
|
||||
this.mCity = cityName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param countryId An identifier for the country (for example ISO alpha-2, ISO alpha-3,
|
||||
* ISO 3166-1 numeric-3, etc)
|
||||
* @return The {@link Builder} instance
|
||||
*/
|
||||
public Builder setCountryId(String countryId) {
|
||||
if (countryId == null) {
|
||||
throw new IllegalArgumentException("Country ID can't be null");
|
||||
}
|
||||
this.mCountryId = countryId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param country The country name
|
||||
* @return The {@link Builder} instance
|
||||
*/
|
||||
public Builder setCountry(String country) {
|
||||
if (country == null) {
|
||||
throw new IllegalArgumentException("Country can't be null");
|
||||
}
|
||||
this.mCountry = country;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param postalCode The postal/ZIP code
|
||||
* @return The {@link Builder} instance
|
||||
*/
|
||||
public Builder setPostalCode(String postalCode) {
|
||||
if (postalCode == null) {
|
||||
throw new IllegalArgumentException("Postal code/ZIP can't be null");
|
||||
}
|
||||
this.mPostal = postalCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param state The state or territory where the city is located
|
||||
* @return The {@link Builder} instance
|
||||
*/
|
||||
public Builder setState(String state) {
|
||||
if (state == null) {
|
||||
throw new IllegalArgumentException("State can't be null");
|
||||
}
|
||||
this.mState = state;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine all of the options that have been set and return a new {@link WeatherLocation}
|
||||
* object
|
||||
* @return {@link WeatherLocation}
|
||||
*/
|
||||
public WeatherLocation build() {
|
||||
WeatherLocation weatherLocation = new WeatherLocation();
|
||||
weatherLocation.mCityId = this.mCityId;
|
||||
weatherLocation.mCity = this.mCity;
|
||||
weatherLocation.mState = this.mState;
|
||||
weatherLocation.mPostal = this.mPostal;
|
||||
weatherLocation.mCountryId = this.mCountryId;
|
||||
weatherLocation.mCountry = this.mCountry;
|
||||
weatherLocation.mKey = UUID.randomUUID().toString();
|
||||
return weatherLocation;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The city ID. This method will return an empty string if the city ID was not set
|
||||
*/
|
||||
public String getCityId() {
|
||||
return mCityId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The city name. This method will return an empty string if the city name was not set
|
||||
*/
|
||||
public String getCity() {
|
||||
return mCity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The state name. This method will return an empty string if the state was not set
|
||||
*/
|
||||
public String getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The postal/ZIP code. This method will return an empty string if the postal/ZIP code
|
||||
* was not set
|
||||
*/
|
||||
public String getPostalCode() {
|
||||
return mPostal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The country ID. This method will return an empty string if the country ID was not set
|
||||
*/
|
||||
public String getCountryId() {
|
||||
return mCountryId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The country name. This method will return an empty string if the country ID was not
|
||||
* set
|
||||
*/
|
||||
public String getCountry() {
|
||||
return mCountry;
|
||||
}
|
||||
|
||||
private WeatherLocation(Parcel in) {
|
||||
// Read parcelable version via the Concierge
|
||||
ParcelInfo parcelInfo = Concierge.receiveParcel(in);
|
||||
int parcelableVersion = parcelInfo.getParcelVersion();
|
||||
|
||||
if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
|
||||
mKey = in.readString();
|
||||
mCityId = in.readString();
|
||||
mCity = in.readString();
|
||||
mState = in.readString();
|
||||
mPostal = in.readString();
|
||||
mCountryId = in.readString();
|
||||
mCountry = in.readString();
|
||||
}
|
||||
|
||||
// Complete parcel info for the concierge
|
||||
parcelInfo.complete();
|
||||
}
|
||||
|
||||
public static final Creator<WeatherLocation> CREATOR = new Creator<WeatherLocation>() {
|
||||
@Override
|
||||
public WeatherLocation createFromParcel(Parcel in) {
|
||||
return new WeatherLocation(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeatherLocation[] newArray(int size) {
|
||||
return new WeatherLocation[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
// Tell the concierge to prepare the parcel
|
||||
ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
|
||||
|
||||
// ==== ELDERBERRY =====
|
||||
dest.writeString(mKey);
|
||||
dest.writeString(mCityId);
|
||||
dest.writeString(mCity);
|
||||
dest.writeString(mState);
|
||||
dest.writeString(mPostal);
|
||||
dest.writeString(mCountryId);
|
||||
dest.writeString(mCountry);
|
||||
|
||||
// Complete parcel info for the concierge
|
||||
parcelInfo.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder()
|
||||
.append("{ City ID: ").append(mCityId)
|
||||
.append(" City: ").append(mCity)
|
||||
.append(" State: ").append(mState)
|
||||
.append(" Postal/ZIP Code: ").append(mPostal)
|
||||
.append(" Country Id: ").append(mCountryId)
|
||||
.append(" Country: ").append(mCountry).append("}")
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
|
||||
if (getClass() == obj.getClass()) {
|
||||
WeatherLocation location = (WeatherLocation) obj;
|
||||
return (TextUtils.equals(mKey, location.mKey));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
84
app/src/main/java/lineageos/weather/util/WeatherUtils.java
Normal file
84
app/src/main/java/lineageos/weather/util/WeatherUtils.java
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weather.util;
|
||||
|
||||
|
||||
import lineageos.providers.WeatherContract;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* Helper class to perform operations and formatting of weather data
|
||||
*/
|
||||
public class WeatherUtils {
|
||||
|
||||
/**
|
||||
* Converts a temperature expressed in degrees Celsius to degrees Fahrenheit
|
||||
* @param celsius temperature in Celsius
|
||||
* @return the temperature in degrees Fahrenheit
|
||||
*/
|
||||
public static double celsiusToFahrenheit(double celsius) {
|
||||
return ((celsius * (9d/5d)) + 32d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a temperature expressed in degrees Fahrenheit to degrees Celsius
|
||||
* @param fahrenheit temperature in Fahrenheit
|
||||
* @return the temperature in degrees Celsius
|
||||
*/
|
||||
public static double fahrenheitToCelsius(double fahrenheit) {
|
||||
return ((fahrenheit - 32d) * (5d/9d));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the temperature and unit supplied. The temperature value
|
||||
* will be half-even rounded.
|
||||
* @param temperature the temperature value
|
||||
* @param tempUnit A valid {@link WeatherContract.WeatherColumns.TempUnit}
|
||||
* @return A string with the format XX°F or XX°C (where XX is the temperature)
|
||||
* depending on the temperature unit that was provided or null if an invalid unit is supplied
|
||||
*/
|
||||
public static String formatTemperature(double temperature, int tempUnit) {
|
||||
if (!isValidTempUnit(tempUnit)) return null;
|
||||
if (Double.isNaN(temperature)) return "-";
|
||||
|
||||
DecimalFormat noDigitsFormat = new DecimalFormat("0");
|
||||
String noDigitsTemp = noDigitsFormat.format(temperature);
|
||||
if (noDigitsTemp.equals("-0")) {
|
||||
noDigitsTemp = "0";
|
||||
}
|
||||
|
||||
StringBuilder formatted = new StringBuilder()
|
||||
.append(noDigitsTemp).append("\u00b0");
|
||||
if (tempUnit == WeatherContract.WeatherColumns.TempUnit.CELSIUS) {
|
||||
formatted.append("C");
|
||||
} else if (tempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
|
||||
formatted.append("F");
|
||||
}
|
||||
return formatted.toString();
|
||||
}
|
||||
|
||||
private static boolean isValidTempUnit(int unit) {
|
||||
switch (unit) {
|
||||
case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
|
||||
case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
161
app/src/main/java/lineageos/weatherservice/ServiceRequest.java
Normal file
161
app/src/main/java/lineageos/weatherservice/ServiceRequest.java
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weatherservice;
|
||||
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import lineageos.weatherservice.IWeatherProviderServiceClient;
|
||||
import lineageos.weather.LineageWeatherManager;
|
||||
import lineageos.weather.RequestInfo;
|
||||
|
||||
/**
|
||||
* This class represents a request submitted by the system to the active weather provider service
|
||||
*/
|
||||
public final class ServiceRequest {
|
||||
|
||||
private final RequestInfo mInfo;
|
||||
private final IWeatherProviderServiceClient mClient;
|
||||
|
||||
private enum Status {
|
||||
IN_PROGRESS, COMPLETED, CANCELLED, FAILED, REJECTED
|
||||
}
|
||||
private Status mStatus;
|
||||
|
||||
/* package */ ServiceRequest(RequestInfo info, IWeatherProviderServiceClient client) {
|
||||
mInfo = info;
|
||||
mClient = client;
|
||||
mStatus = Status.IN_PROGRESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the request information
|
||||
* @return {@link lineageos.weather.RequestInfo}
|
||||
*/
|
||||
public RequestInfo getRequestInfo() {
|
||||
return mInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be called once the request has been completed
|
||||
*/
|
||||
public void complete(@NonNull ServiceRequestResult result) {
|
||||
synchronized (this) {
|
||||
if (mStatus.equals(Status.IN_PROGRESS)) {
|
||||
try {
|
||||
final int requestType = mInfo.getRequestType();
|
||||
switch (requestType) {
|
||||
case RequestInfo.TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||
case RequestInfo.TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||
if (result.getWeatherInfo() == null) {
|
||||
throw new IllegalStateException("The service request result doesn't"
|
||||
+ " contain a valid WeatherInfo object");
|
||||
}
|
||||
mClient.setServiceRequestState(mInfo, result,
|
||||
LineageWeatherManager.RequestStatus.COMPLETED);
|
||||
break;
|
||||
case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ:
|
||||
if (result.getLocationLookupList() == null
|
||||
|| result.getLocationLookupList().size() <= 0) {
|
||||
//In case the user decided to mark this request as completed with
|
||||
//null or empty list. It's not necessarily a failure
|
||||
mClient.setServiceRequestState(mInfo, null,
|
||||
LineageWeatherManager.RequestStatus.NO_MATCH_FOUND);
|
||||
} else {
|
||||
mClient.setServiceRequestState(mInfo, result,
|
||||
LineageWeatherManager.RequestStatus.COMPLETED);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
mStatus = Status.COMPLETED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be called if the service failed to process the request
|
||||
* (no internet connection, time out, etc.)
|
||||
*/
|
||||
public void fail() {
|
||||
synchronized (this) {
|
||||
if (mStatus.equals(Status.IN_PROGRESS)) {
|
||||
try {
|
||||
final int requestType = mInfo.getRequestType();
|
||||
switch (requestType) {
|
||||
case RequestInfo.TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||
case RequestInfo.TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||
mClient.setServiceRequestState(mInfo, null,
|
||||
LineageWeatherManager.RequestStatus.FAILED);
|
||||
break;
|
||||
case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ:
|
||||
mClient.setServiceRequestState(mInfo, null,
|
||||
LineageWeatherManager.RequestStatus.FAILED);
|
||||
break;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
mStatus = Status.FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be called if the service decides not to honor the request. Note this
|
||||
* method will accept only the following values.
|
||||
* <ul>
|
||||
* <li>{@link lineageos.weather.LineageWeatherManager.RequestStatus#SUBMITTED_TOO_SOON}</li>
|
||||
* <li>{@link lineageos.weather.LineageWeatherManager.RequestStatus#ALREADY_IN_PROGRESS}</li>
|
||||
* </ul>
|
||||
* Attempting to pass any other value will get you an IllegalArgumentException
|
||||
* @param status
|
||||
*/
|
||||
public void reject(int status) {
|
||||
synchronized (this) {
|
||||
if (mStatus.equals(Status.IN_PROGRESS)) {
|
||||
switch (status) {
|
||||
case LineageWeatherManager.RequestStatus.ALREADY_IN_PROGRESS:
|
||||
case LineageWeatherManager.RequestStatus.SUBMITTED_TOO_SOON:
|
||||
try {
|
||||
mClient.setServiceRequestState(mInfo, null, status);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Can't reject with status " + status);
|
||||
}
|
||||
mStatus = Status.REJECTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the WeatherProviderService base class to notify we don't want this request anymore.
|
||||
* The service implementing the WeatherProviderService will be notified of this action
|
||||
* via onRequestCancelled()
|
||||
* @hide
|
||||
*/
|
||||
public void cancel() {
|
||||
synchronized (this) {
|
||||
mStatus = Status.CANCELLED;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weatherservice;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import lineageos.os.Build;
|
||||
import lineageos.os.Concierge;
|
||||
import lineageos.os.Concierge.ParcelInfo;
|
||||
import lineageos.weather.WeatherLocation;
|
||||
import lineageos.weather.WeatherInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Use this class to build a request result.
|
||||
*/
|
||||
public final class ServiceRequestResult implements Parcelable {
|
||||
|
||||
private WeatherInfo mWeatherInfo;
|
||||
private List<WeatherLocation> mLocationLookupList;
|
||||
private String mKey;
|
||||
|
||||
private ServiceRequestResult() {}
|
||||
|
||||
private ServiceRequestResult(Parcel in) {
|
||||
// Read parcelable version via the Concierge
|
||||
ParcelInfo parcelInfo = Concierge.receiveParcel(in);
|
||||
int parcelableVersion = parcelInfo.getParcelVersion();
|
||||
|
||||
if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
|
||||
mKey = in.readString();
|
||||
int hasWeatherInfo = in.readInt();
|
||||
if (hasWeatherInfo == 1) {
|
||||
mWeatherInfo = WeatherInfo.CREATOR.createFromParcel(in);
|
||||
}
|
||||
int hasLocationLookupList = in.readInt();
|
||||
if (hasLocationLookupList == 1) {
|
||||
mLocationLookupList = new ArrayList<>();
|
||||
int listSize = in.readInt();
|
||||
while (listSize > 0) {
|
||||
mLocationLookupList.add(WeatherLocation.CREATOR.createFromParcel(in));
|
||||
listSize--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Complete parcel info for the concierge
|
||||
parcelInfo.complete();
|
||||
}
|
||||
|
||||
public static final Creator<ServiceRequestResult> CREATOR
|
||||
= new Creator<ServiceRequestResult>() {
|
||||
@Override
|
||||
public ServiceRequestResult createFromParcel(Parcel in) {
|
||||
return new ServiceRequestResult(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceRequestResult[] newArray(int size) {
|
||||
return new ServiceRequestResult[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
// Tell the concierge to prepare the parcel
|
||||
ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
|
||||
|
||||
// ==== ELDERBERRY =====
|
||||
dest.writeString(mKey);
|
||||
if (mWeatherInfo != null) {
|
||||
dest.writeInt(1);
|
||||
mWeatherInfo.writeToParcel(dest, 0);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
if (mLocationLookupList != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeInt(mLocationLookupList.size());
|
||||
for (WeatherLocation lookup : mLocationLookupList) {
|
||||
lookup.writeToParcel(dest, 0);
|
||||
}
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
|
||||
// Complete parcel info for the concierge
|
||||
parcelInfo.complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for {@link ServiceRequestResult}
|
||||
*/
|
||||
public static class Builder {
|
||||
private WeatherInfo mWeatherInfo;
|
||||
private List<WeatherLocation> mLocationLookupList;
|
||||
public Builder() {
|
||||
this.mWeatherInfo = null;
|
||||
this.mLocationLookupList = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param weatherInfo The WeatherInfo object holding the data that will be used to update
|
||||
* the weather content provider
|
||||
*/
|
||||
public Builder(@NonNull WeatherInfo weatherInfo) {
|
||||
if (weatherInfo == null) {
|
||||
throw new IllegalArgumentException("WeatherInfo can't be null");
|
||||
}
|
||||
|
||||
mWeatherInfo = weatherInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param locations The list of WeatherLocation objects. The list should not be null
|
||||
*/
|
||||
public Builder(@NonNull List<WeatherLocation> locations) {
|
||||
if (locations == null) {
|
||||
throw new IllegalArgumentException("Weather location list can't be null");
|
||||
}
|
||||
mLocationLookupList = locations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ServiceRequestResult} with the arguments
|
||||
* supplied to this builder
|
||||
* @return {@link ServiceRequestResult}
|
||||
*/
|
||||
public ServiceRequestResult build() {
|
||||
ServiceRequestResult result = new ServiceRequestResult();
|
||||
result.mWeatherInfo = this.mWeatherInfo;
|
||||
result.mLocationLookupList = this.mLocationLookupList;
|
||||
result.mKey = UUID.randomUUID().toString();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The WeatherInfo object supplied by the weather provider service
|
||||
*/
|
||||
public WeatherInfo getWeatherInfo() {
|
||||
return mWeatherInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The list of WeatherLocation objects supplied by the weather provider service
|
||||
*/
|
||||
public List<WeatherLocation> getLocationLookupList() {
|
||||
return new ArrayList<>(mLocationLookupList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
|
||||
if (getClass() == obj.getClass()) {
|
||||
ServiceRequestResult request = (ServiceRequestResult) obj;
|
||||
return (TextUtils.equals(mKey, request.mKey));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The CyanogenMod 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 lineageos.weatherservice;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import lineageos.weather.RequestInfo;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
|
||||
public abstract class WeatherProviderService extends Service {
|
||||
|
||||
private Handler mHandler;
|
||||
private IWeatherProviderServiceClient mClient;
|
||||
private Set<ServiceRequest> mWeakRequestsSet
|
||||
= Collections.newSetFromMap(new WeakHashMap<ServiceRequest, Boolean>());
|
||||
|
||||
/**
|
||||
* The {@link android.content.Intent} action that must be declared as handled by a service in
|
||||
* its manifest for the system to recognize it as a weather provider service
|
||||
*/
|
||||
public static final String SERVICE_INTERFACE
|
||||
= "lineageos.weatherservice.WeatherProviderService";
|
||||
|
||||
/**
|
||||
* Name under which a {@link WeatherProviderService} publishes information about itself.
|
||||
* This meta-data must reference an XML resource containing
|
||||
* a <code><weather-provider-service></code>
|
||||
* tag.
|
||||
*/
|
||||
public static final String SERVICE_META_DATA = "lineageos.weatherservice";
|
||||
|
||||
@Override
|
||||
protected final void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
mHandler = new ServiceHandler(base.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
private final IWeatherProviderService.Stub mBinder = new IWeatherProviderService.Stub() {
|
||||
|
||||
@Override
|
||||
public void processWeatherUpdateRequest(final RequestInfo info) {
|
||||
mHandler.obtainMessage(ServiceHandler.MSG_ON_NEW_REQUEST, info).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processCityNameLookupRequest(final RequestInfo info) {
|
||||
mHandler.obtainMessage(ServiceHandler.MSG_ON_NEW_REQUEST, info).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceClient(IWeatherProviderServiceClient client) {
|
||||
mHandler.obtainMessage(ServiceHandler.MSG_SET_CLIENT, client).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelOngoingRequests() {
|
||||
synchronized (mWeakRequestsSet) {
|
||||
for (final ServiceRequest request : mWeakRequestsSet) {
|
||||
request.cancel();
|
||||
mWeakRequestsSet.remove(request);
|
||||
mHandler.obtainMessage(ServiceHandler.MSG_CANCEL_REQUEST, request)
|
||||
.sendToTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelRequest(int requestId) {
|
||||
synchronized (mWeakRequestsSet) {
|
||||
for (final ServiceRequest request : mWeakRequestsSet) {
|
||||
if (request.getRequestInfo().hashCode() == requestId) {
|
||||
mWeakRequestsSet.remove(request);
|
||||
request.cancel();
|
||||
mHandler.obtainMessage(ServiceHandler.MSG_CANCEL_REQUEST, request)
|
||||
.sendToTarget();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private class ServiceHandler extends Handler {
|
||||
|
||||
public ServiceHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
public static final int MSG_SET_CLIENT = 1;
|
||||
public static final int MSG_ON_NEW_REQUEST = 2;
|
||||
public static final int MSG_CANCEL_REQUEST = 3;
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_SET_CLIENT: {
|
||||
mClient = (IWeatherProviderServiceClient) msg.obj;
|
||||
if (mClient != null) {
|
||||
onConnected();
|
||||
} else {
|
||||
onDisconnected();
|
||||
}
|
||||
return;
|
||||
}
|
||||
case MSG_ON_NEW_REQUEST: {
|
||||
RequestInfo info = (RequestInfo) msg.obj;
|
||||
if (info != null) {
|
||||
ServiceRequest request = new ServiceRequest(info, mClient);
|
||||
synchronized (mWeakRequestsSet) {
|
||||
mWeakRequestsSet.add(request);
|
||||
}
|
||||
onRequestSubmitted(request);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case MSG_CANCEL_REQUEST: {
|
||||
ServiceRequest request = (ServiceRequest) msg.obj;
|
||||
onRequestCancelled(request);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The system has connected to this service.
|
||||
*/
|
||||
protected void onConnected() {
|
||||
/* Do nothing */
|
||||
}
|
||||
|
||||
/**
|
||||
* The system has disconnected from this service.
|
||||
*/
|
||||
protected void onDisconnected() {
|
||||
/* Do nothing */
|
||||
}
|
||||
|
||||
/**
|
||||
* A new request has been submitted to this service
|
||||
* @param request The service request to be processed by this service
|
||||
*/
|
||||
protected abstract void onRequestSubmitted(ServiceRequest request);
|
||||
|
||||
/**
|
||||
* Called when the system is not interested on this request anymore. Note that the service
|
||||
* <b>has marked the request as cancelled</b> and you must stop any ongoing operation
|
||||
* (such as pulling data from internet) that this service could've been performing to honor the
|
||||
* request.
|
||||
*
|
||||
* @param request The request cancelled by the system
|
||||
*/
|
||||
protected abstract void onRequestCancelled(ServiceRequest request);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Martin, Normano64, Pavel Elagin, Taavi Eomäe
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Martin, Matthieu Baerts, Normano64, Pavel Elagin, Taavi Eomäe
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -39,12 +39,16 @@ 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 android.util.TypedValue;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@ -58,11 +62,14 @@ 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.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothStateChangeReceiver;
|
||||
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.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.NotificationCollectorMonitorService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
@ -71,6 +78,17 @@ import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITBIP;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITCOR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.AMAZFITCOR2;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.HPLUS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.ID115;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND2;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND3;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.MIBAND4;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.ZETIME;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.fromKey;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_ID;
|
||||
|
||||
/**
|
||||
@ -88,7 +106,7 @@ public class GBApplication extends Application {
|
||||
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 final int CURRENT_PREFS_VERSION = 7;
|
||||
private static LimitedQueue mIDSenderLookup = new LimitedQueue(16);
|
||||
private static Prefs prefs;
|
||||
private static GBPrefs gbPrefs;
|
||||
@ -101,6 +119,7 @@ public class GBApplication extends Application {
|
||||
public static final String ACTION_QUIT
|
||||
= "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit";
|
||||
public static final String ACTION_LANGUAGE_CHANGE = "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.language_change";
|
||||
public static final String ACTION_NEW_DATA = "nodomain.freeyourgadget.gadgetbridge.action.new_data";
|
||||
|
||||
private static GBApplication app;
|
||||
|
||||
@ -183,9 +202,9 @@ public class GBApplication extends Application {
|
||||
if (isRunningMarshmallowOrLater()) {
|
||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
//the following will ensure the notification manager is kept alive
|
||||
if(isRunningOreoOrLater()) {
|
||||
if (isRunningOreoOrLater()) {
|
||||
NotificationChannel channel = notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID);
|
||||
if(channel == null) {
|
||||
if (channel == null) {
|
||||
channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
|
||||
getString(R.string.notification_channel_name),
|
||||
NotificationManager.IMPORTANCE_LOW);
|
||||
@ -228,7 +247,7 @@ public class GBApplication extends Application {
|
||||
logging.setupLogging(enabled);
|
||||
}
|
||||
|
||||
public static String getLogPath(){
|
||||
public static String getLogPath() {
|
||||
return logging.getLogPath();
|
||||
}
|
||||
|
||||
@ -316,11 +335,12 @@ public class GBApplication extends Application {
|
||||
public static boolean isRunningMarshmallowOrLater() {
|
||||
return VERSION.SDK_INT >= Build.VERSION_CODES.M;
|
||||
}
|
||||
|
||||
public static boolean isRunningNougatOrLater() {
|
||||
return VERSION.SDK_INT >= Build.VERSION_CODES.N;
|
||||
}
|
||||
|
||||
public static boolean isRunningOreoOrLater(){
|
||||
public static boolean isRunningOreoOrLater() {
|
||||
return VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||
}
|
||||
|
||||
@ -399,7 +419,7 @@ public class GBApplication extends Application {
|
||||
|
||||
private static void loadAppsNotifBlackList() {
|
||||
GB.log("Loading apps_notification_blacklist", GB.INFO, null);
|
||||
apps_notification_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.PACKAGE_BLACKLIST, null);
|
||||
apps_notification_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.PACKAGE_BLACKLIST, null); // lgtm [java/abstract-to-concrete-cast]
|
||||
if (apps_notification_blacklist == null) {
|
||||
apps_notification_blacklist = new HashSet<>();
|
||||
}
|
||||
@ -451,7 +471,7 @@ public class GBApplication extends Application {
|
||||
|
||||
private static void loadAppsPebbleBlackList() {
|
||||
GB.log("Loading apps_pebblemsg_blacklist", GB.INFO, null);
|
||||
apps_pebblemsg_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.PACKAGE_PEBBLEMSG_BLACKLIST, null);
|
||||
apps_pebblemsg_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.PACKAGE_PEBBLEMSG_BLACKLIST, null); // lgtm [java/abstract-to-concrete-cast]
|
||||
if (apps_pebblemsg_blacklist == null) {
|
||||
apps_pebblemsg_blacklist = new HashSet<>();
|
||||
}
|
||||
@ -481,14 +501,14 @@ public class GBApplication extends Application {
|
||||
saveAppsPebbleBlackList();
|
||||
}
|
||||
|
||||
public static String packageNameToPebbleMsgSender(String packageName) {
|
||||
if ("eu.siacs.conversations".equals(packageName)){
|
||||
return("Conversations");
|
||||
} else if ("net.osmand.plus".equals(packageName)) {
|
||||
return("OsmAnd");
|
||||
public static String packageNameToPebbleMsgSender(String packageName) {
|
||||
if ("eu.siacs.conversations".equals(packageName)) {
|
||||
return ("Conversations");
|
||||
} else if ("net.osmand.plus".equals(packageName)) {
|
||||
return ("OsmAnd");
|
||||
}
|
||||
return packageName;
|
||||
}
|
||||
return packageName;
|
||||
}
|
||||
|
||||
private static HashSet<String> calendars_blacklist = null;
|
||||
|
||||
@ -523,7 +543,7 @@ public static String packageNameToPebbleMsgSender(String packageName) {
|
||||
|
||||
private static void loadCalendarsBlackList() {
|
||||
GB.log("Loading calendars_blacklist", GB.INFO, null);
|
||||
calendars_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.CALENDAR_BLACKLIST, null);
|
||||
calendars_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.CALENDAR_BLACKLIST, null); // lgtm [java/abstract-to-concrete-cast]
|
||||
if (calendars_blacklist == null) {
|
||||
calendars_blacklist = new HashSet<>();
|
||||
}
|
||||
@ -579,49 +599,310 @@ public static String packageNameToPebbleMsgSender(String packageName) {
|
||||
}
|
||||
}
|
||||
|
||||
private void migrateStringPrefToPerDevicePref(String globalPref, String globalPrefDefault, String perDevicePref, ArrayList<DeviceType> deviceTypes) {
|
||||
SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
String globalPrefValue = prefs.getString(globalPref, globalPrefDefault);
|
||||
try (DBHandler db = acquireDB()) {
|
||||
DaoSession daoSession = db.getDaoSession();
|
||||
List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
|
||||
for (Device dbDevice : activeDevices) {
|
||||
SharedPreferences deviceSpecificSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
|
||||
if (deviceSpecificSharedPrefs != null) {
|
||||
SharedPreferences.Editor deviceSharedPrefsEdit = deviceSpecificSharedPrefs.edit();
|
||||
DeviceType deviceType = fromKey(dbDevice.getType());
|
||||
|
||||
if (deviceTypes.contains(deviceType)) {
|
||||
Log.i(TAG, "migrating global string preference " + globalPref + " for " + deviceType.name() + " " + dbDevice.getIdentifier() );
|
||||
deviceSharedPrefsEdit.putString(perDevicePref, globalPrefValue);
|
||||
}
|
||||
deviceSharedPrefsEdit.apply();
|
||||
}
|
||||
}
|
||||
editor.remove(globalPref);
|
||||
editor.apply();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "error acquiring DB lock");
|
||||
}
|
||||
}
|
||||
|
||||
private void migrateBooleanPrefToPerDevicePref(String globalPref, Boolean globalPrefDefault, String perDevicePref, ArrayList<DeviceType> deviceTypes) {
|
||||
SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
boolean globalPrefValue = prefs.getBoolean(globalPref, globalPrefDefault);
|
||||
try (DBHandler db = acquireDB()) {
|
||||
DaoSession daoSession = db.getDaoSession();
|
||||
List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
|
||||
for (Device dbDevice : activeDevices) {
|
||||
SharedPreferences deviceSpecificSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
|
||||
if (deviceSpecificSharedPrefs != null) {
|
||||
SharedPreferences.Editor deviceSharedPrefsEdit = deviceSpecificSharedPrefs.edit();
|
||||
DeviceType deviceType = fromKey(dbDevice.getType());
|
||||
|
||||
if (deviceTypes.contains(deviceType)) {
|
||||
Log.i(TAG, "migrating global boolean preference " + globalPref + " for " + deviceType.name() + " " + dbDevice.getIdentifier() );
|
||||
deviceSharedPrefsEdit.putBoolean(perDevicePref, globalPrefValue);
|
||||
}
|
||||
deviceSharedPrefsEdit.apply();
|
||||
}
|
||||
}
|
||||
editor.remove(globalPref);
|
||||
editor.apply();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "error acquiring DB lock");
|
||||
}
|
||||
}
|
||||
|
||||
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 legacyWeight = 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 (legacyWeight != null) {
|
||||
editor.putString(ActivityUser.PREF_USER_WEIGHT_KG, legacyWeight);
|
||||
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;
|
||||
if (oldVersion == 0) {
|
||||
String legacyGender = sharedPrefs.getString("mi_user_gender", null);
|
||||
String legacyHeight = sharedPrefs.getString("mi_user_height_cm", null);
|
||||
String legacyWeight = 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 (legacyWeight != null) {
|
||||
editor.putString(ActivityUser.PREF_USER_WEIGHT_KG, legacyWeight);
|
||||
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");
|
||||
}
|
||||
}
|
||||
if (oldVersion < 2) {
|
||||
//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));
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
try (DBHandler db = acquireDB()) {
|
||||
DaoSession daoSession = db.getDaoSession();
|
||||
List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
|
||||
for (Device dbDevice : activeDevices) {
|
||||
SharedPreferences deviceSpecificSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
|
||||
if (deviceSpecificSharedPrefs != null) {
|
||||
SharedPreferences.Editor deviceSharedPrefsEdit = deviceSpecificSharedPrefs.edit();
|
||||
String preferenceKey = dbDevice.getIdentifier() + "_lastSportsActivityTimeMillis";
|
||||
long lastSportsActivityTimeMillis = sharedPrefs.getLong(preferenceKey, 0);
|
||||
if (lastSportsActivityTimeMillis != 0) {
|
||||
deviceSharedPrefsEdit.putLong("lastSportsActivityTimeMillis", lastSportsActivityTimeMillis);
|
||||
editor.remove(preferenceKey);
|
||||
}
|
||||
preferenceKey = dbDevice.getIdentifier() + "_lastSyncTimeMillis";
|
||||
long lastSyncTimeMillis = sharedPrefs.getLong(preferenceKey, 0);
|
||||
if (lastSyncTimeMillis != 0) {
|
||||
deviceSharedPrefsEdit.putLong("lastSyncTimeMillis", lastSyncTimeMillis);
|
||||
editor.remove(preferenceKey);
|
||||
}
|
||||
|
||||
String newLanguage = null;
|
||||
Set<String> displayItems = null;
|
||||
|
||||
DeviceType deviceType = fromKey(dbDevice.getType());
|
||||
|
||||
if (deviceType == AMAZFITBIP || deviceType == AMAZFITCOR || deviceType == AMAZFITCOR2) {
|
||||
int oldLanguage = prefs.getInt("amazfitbip_language", -1);
|
||||
newLanguage = "auto";
|
||||
String[] oldLanguageLookup = {"zh_CN", "zh_TW", "en_US", "es_ES", "ru_RU", "de_DE", "it_IT", "fr_FR", "tr_TR"};
|
||||
if (oldLanguage >= 0 && oldLanguage < oldLanguageLookup.length) {
|
||||
newLanguage = oldLanguageLookup[oldLanguage];
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceType == AMAZFITBIP || deviceType == AMAZFITCOR) {
|
||||
deviceSharedPrefsEdit.putString("disconnect_notification", prefs.getString("disconnect_notification", "off"));
|
||||
deviceSharedPrefsEdit.putString("disconnect_notification_start", prefs.getString("disconnect_notification_start", "8:00"));
|
||||
deviceSharedPrefsEdit.putString("disconnect_notification_end", prefs.getString("disconnect_notification_end", "22:00"));
|
||||
}
|
||||
if (deviceType == MIBAND2 || deviceType == MIBAND3) {
|
||||
deviceSharedPrefsEdit.putString("do_not_disturb", prefs.getString("mi2_do_not_disturb", "off"));
|
||||
deviceSharedPrefsEdit.putString("do_not_disturb_start", prefs.getString("mi2_do_not_disturb_start", "1:00"));
|
||||
deviceSharedPrefsEdit.putString("do_not_disturb_end", prefs.getString("mi2_do_not_disturb_end", "6:00"));
|
||||
}
|
||||
if (dbDevice.getManufacturer().equals("Huami")) {
|
||||
deviceSharedPrefsEdit.putString("activate_display_on_lift_wrist", prefs.getString("activate_display_on_lift_wrist", "off"));
|
||||
deviceSharedPrefsEdit.putString("display_on_lift_start", prefs.getString("display_on_lift_start", "0:00"));
|
||||
deviceSharedPrefsEdit.putString("display_on_lift_end", prefs.getString("display_on_lift_end", "0:00"));
|
||||
}
|
||||
switch (deviceType) {
|
||||
case MIBAND:
|
||||
deviceSharedPrefsEdit.putBoolean("low_latency_fw_update", prefs.getBoolean("mi_low_latency_fw_update", true));
|
||||
deviceSharedPrefsEdit.putString("device_time_offset_hours", String.valueOf(prefs.getInt("mi_device_time_offset_hours", 0)));
|
||||
break;
|
||||
case AMAZFITCOR:
|
||||
displayItems = prefs.getStringSet("cor_display_items", null);
|
||||
break;
|
||||
case AMAZFITBIP:
|
||||
displayItems = prefs.getStringSet("bip_display_items", null);
|
||||
break;
|
||||
case MIBAND2:
|
||||
displayItems = prefs.getStringSet("mi2_display_items", null);
|
||||
deviceSharedPrefsEdit.putBoolean("mi2_enable_text_notifications", prefs.getBoolean("mi2_enable_text_notifications", true));
|
||||
deviceSharedPrefsEdit.putString("mi2_dateformat", prefs.getString("mi2_dateformat", "dateformat_time"));
|
||||
deviceSharedPrefsEdit.putBoolean("rotate_wrist_to_cycle_info", prefs.getBoolean("mi2_rotate_wrist_to_switch_info", false));
|
||||
break;
|
||||
case MIBAND3:
|
||||
newLanguage = prefs.getString("miband3_language", "auto");
|
||||
displayItems = prefs.getStringSet("miband3_display_items", null);
|
||||
deviceSharedPrefsEdit.putBoolean("swipe_unlock", prefs.getBoolean("mi3_band_screen_unlock", false));
|
||||
deviceSharedPrefsEdit.putString("night_mode", prefs.getString("mi3_night_mode", "off"));
|
||||
deviceSharedPrefsEdit.putString("night_mode_start", prefs.getString("mi3_night_mode_start", "16:00"));
|
||||
deviceSharedPrefsEdit.putString("night_mode_end", prefs.getString("mi3_night_mode_end", "7:00"));
|
||||
|
||||
}
|
||||
if (displayItems != null) {
|
||||
deviceSharedPrefsEdit.putStringSet("display_items", displayItems);
|
||||
}
|
||||
if (newLanguage != null) {
|
||||
deviceSharedPrefsEdit.putString("language", newLanguage);
|
||||
}
|
||||
deviceSharedPrefsEdit.apply();
|
||||
}
|
||||
}
|
||||
editor.remove("amazfitbip_language");
|
||||
editor.remove("bip_display_items");
|
||||
editor.remove("cor_display_items");
|
||||
editor.remove("disconnect_notification");
|
||||
editor.remove("disconnect_notification_start");
|
||||
editor.remove("disconnect_notification_end");
|
||||
editor.remove("activate_display_on_lift_wrist");
|
||||
editor.remove("display_on_lift_start");
|
||||
editor.remove("display_on_lift_end");
|
||||
|
||||
editor.remove("mi_low_latency_fw_update");
|
||||
editor.remove("mi_device_time_offset_hours");
|
||||
editor.remove("mi2_do_not_disturb");
|
||||
editor.remove("mi2_do_not_disturb_start");
|
||||
editor.remove("mi2_do_not_disturb_end");
|
||||
editor.remove("mi2_dateformat");
|
||||
editor.remove("mi2_display_items");
|
||||
editor.remove("mi2_rotate_wrist_to_switch_info");
|
||||
editor.remove("mi2_enable_text_notifications");
|
||||
editor.remove("mi3_band_screen_unlock");
|
||||
editor.remove("mi3_night_mode");
|
||||
editor.remove("mi3_night_mode_start");
|
||||
editor.remove("mi3_night_mode_end");
|
||||
editor.remove("miband3_language");
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "error acquiring DB lock");
|
||||
}
|
||||
}
|
||||
if (oldVersion < 4) {
|
||||
try (DBHandler db = acquireDB()) {
|
||||
DaoSession daoSession = db.getDaoSession();
|
||||
List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
|
||||
for (Device dbDevice : activeDevices) {
|
||||
SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
|
||||
SharedPreferences.Editor deviceSharedPrefsEdit = deviceSharedPrefs.edit();
|
||||
DeviceType deviceType = fromKey(dbDevice.getType());
|
||||
|
||||
if (deviceType == MIBAND) {
|
||||
int deviceTimeOffsetHours = deviceSharedPrefs.getInt("device_time_offset_hours",0);
|
||||
deviceSharedPrefsEdit.putString("device_time_offset_hours", Integer.toString(deviceTimeOffsetHours) );
|
||||
}
|
||||
|
||||
deviceSharedPrefsEdit.apply();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "error acquiring DB lock");
|
||||
}
|
||||
}
|
||||
if (oldVersion < 5) {
|
||||
try (DBHandler db = acquireDB()) {
|
||||
DaoSession daoSession = db.getDaoSession();
|
||||
List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
|
||||
for (Device dbDevice : activeDevices) {
|
||||
SharedPreferences deviceSpecificSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
|
||||
if (deviceSpecificSharedPrefs != null) {
|
||||
SharedPreferences.Editor deviceSharedPrefsEdit = deviceSpecificSharedPrefs.edit();
|
||||
DeviceType deviceType = fromKey(dbDevice.getType());
|
||||
|
||||
String newWearside = null;
|
||||
String newOrientation = null;
|
||||
String newTimeformat = null;
|
||||
switch (deviceType) {
|
||||
case AMAZFITBIP:
|
||||
case AMAZFITCOR:
|
||||
case AMAZFITCOR2:
|
||||
case MIBAND:
|
||||
case MIBAND2:
|
||||
case MIBAND3:
|
||||
case MIBAND4:
|
||||
newWearside = prefs.getString("mi_wearside", "left");
|
||||
break;
|
||||
case HPLUS:
|
||||
newWearside = prefs.getString("hplus_wrist", "left");
|
||||
newTimeformat = prefs.getString("hplus_timeformat", "24h");
|
||||
break;
|
||||
case ID115:
|
||||
newWearside = prefs.getString("id115_wrist", "left");
|
||||
newOrientation = prefs.getString("id115_screen_orientation", "horizontal");
|
||||
break;
|
||||
case ZETIME:
|
||||
newWearside = prefs.getString("zetime_wrist", "left");
|
||||
newTimeformat = prefs.getInt("zetime_timeformat", 1) == 2 ? "am/pm" : "24h";
|
||||
break;
|
||||
}
|
||||
if (newWearside != null) {
|
||||
deviceSharedPrefsEdit.putString("wearlocation", newWearside);
|
||||
}
|
||||
if (newOrientation != null) {
|
||||
deviceSharedPrefsEdit.putString("screen_orientation", newOrientation);
|
||||
}
|
||||
if (newTimeformat != null) {
|
||||
deviceSharedPrefsEdit.putString("timeformat", newTimeformat);
|
||||
}
|
||||
deviceSharedPrefsEdit.apply();
|
||||
}
|
||||
}
|
||||
editor.remove("hplus_timeformat");
|
||||
editor.remove("hplus_wrist");
|
||||
editor.remove("id115_wrist");
|
||||
editor.remove("id115_screen_orientation");
|
||||
editor.remove("mi_wearside");
|
||||
editor.remove("zetime_timeformat");
|
||||
editor.remove("zetime_wrist");
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "error acquiring DB lock");
|
||||
}
|
||||
}
|
||||
if (oldVersion < 6) {
|
||||
migrateBooleanPrefToPerDevicePref("mi2_enable_button_action", false, "button_action_enable", new ArrayList<>(Collections.singletonList(MIBAND2)));
|
||||
migrateBooleanPrefToPerDevicePref("mi2_button_action_vibrate", false, "button_action_vibrate", new ArrayList<>(Collections.singletonList(MIBAND2)));
|
||||
migrateStringPrefToPerDevicePref("mi_button_press_count", "6", "button_action_press_count", new ArrayList<>(Collections.singletonList(MIBAND2)));
|
||||
migrateStringPrefToPerDevicePref("mi_button_press_count_max_delay", "2000", "button_action_press_max_interval", new ArrayList<>(Collections.singletonList(MIBAND2)));
|
||||
migrateStringPrefToPerDevicePref("mi_button_press_count_match_delay", "0", "button_action_broadcast_delay", new ArrayList<>(Collections.singletonList(MIBAND2)));
|
||||
migrateStringPrefToPerDevicePref("mi_button_press_broadcast", "nodomain.freeyourgadget.gadgetbridge.ButtonPressed", "button_action_broadcast", new ArrayList<>(Collections.singletonList(MIBAND2)));
|
||||
}
|
||||
if (oldVersion < 7) {
|
||||
migrateStringPrefToPerDevicePref("mi_reserve_alarm_calendar","0","reserve_alarms_calendar", new ArrayList<>(Arrays.asList(MIBAND, MIBAND2)));
|
||||
}
|
||||
|
||||
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static SharedPreferences getDeviceSpecificSharedPrefs(String deviceIdentifier) {
|
||||
if (deviceIdentifier == null || deviceIdentifier.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return context.getSharedPreferences("devicesettings_" + deviceIdentifier, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static void setLanguage(String lang) {
|
||||
if (lang.equals("default")) {
|
||||
language = Resources.getSystem().getConfiguration().locale;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Taavi Eomäe
|
||||
/* Copyright (C) 2016-2019 Andreas Shimokawa, Carsten Pfeiffer, Taavi Eomäe
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2018 Carsten Pfeiffer, Daniele Gobbetti, Pavel Elagin
|
||||
/* Copyright (C) 2016-2019 Carsten Pfeiffer, Daniele Gobbetti, Pavel Elagin
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2019 Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2018 0nse, Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2019 0nse, Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -27,12 +27,15 @@ import android.os.Build;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
@ -55,7 +58,8 @@ public class SleepAlarmWidget extends AppWidgetProvider {
|
||||
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.sleep_alarm_widget);
|
||||
|
||||
// Add our own click intent
|
||||
Intent intent = new Intent(ACTION);
|
||||
Intent intent = new Intent(context, SleepAlarmWidget.class);
|
||||
intent.setAction(ACTION);
|
||||
PendingIntent clickPI = PendingIntent.getBroadcast(
|
||||
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
views.setOnClickPendingIntent(R.id.sleepalarmwidget_text, clickPI);
|
||||
@ -90,23 +94,41 @@ public class SleepAlarmWidget extends AppWidgetProvider {
|
||||
// current timestamp
|
||||
GregorianCalendar calendar = new GregorianCalendar();
|
||||
// add preferred sleep duration
|
||||
calendar.add(Calendar.HOUR_OF_DAY, userSleepDuration);
|
||||
if (userSleepDuration > 0) {
|
||||
calendar.add(Calendar.HOUR_OF_DAY, userSleepDuration);
|
||||
} else { // probably testing
|
||||
calendar.add(Calendar.MINUTE, 1);
|
||||
}
|
||||
|
||||
// overwrite the first alarm and activate it, without
|
||||
|
||||
// overwrite the first alarm and activate it
|
||||
GBAlarm alarm = GBAlarm.createSingleShot(0, true, calendar);
|
||||
alarm.store();
|
||||
|
||||
if (GBApplication.isRunningLollipopOrLater()) {
|
||||
setAlarmViaAlarmManager(context, calendar.getTimeInMillis());
|
||||
Context appContext = context.getApplicationContext();
|
||||
if (appContext instanceof GBApplication) {
|
||||
GBApplication gbApp = (GBApplication) appContext;
|
||||
GBDevice selectedDevice = gbApp.getDeviceManager().getSelectedDevice();
|
||||
if (selectedDevice == null || !selectedDevice.isInitialized()) {
|
||||
GB.toast(context,
|
||||
context.getString(R.string.appwidget_not_connected),
|
||||
Toast.LENGTH_LONG, GB.WARN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
context.getString(R.string.appwidget_setting_alarm, hours, minutes),
|
||||
Toast.LENGTH_SHORT, GB.INFO);
|
||||
|
||||
Alarm alarm = AlarmUtils.createSingleShot(0,true, calendar);
|
||||
ArrayList<Alarm> alarms = new ArrayList<>(1);
|
||||
alarms.add(alarm);
|
||||
GBApplication.deviceService().onSetAlarms(alarms);
|
||||
|
||||
// if (GBApplication.isRunningLollipopOrLater()) {
|
||||
// setAlarmViaAlarmManager(context, calendar.getTimeInMillis());
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,225 @@
|
||||
/* Copyright (C) 2019 Andreas Shimokawa, vanous
|
||||
|
||||
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.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProvider;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.WidgetAlarmsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
|
||||
public class Widget extends AppWidgetProvider {
|
||||
public static final String WIDGET_CLICK = "nodomain.freeyourgadget.gadgetbridge.WidgetClick";
|
||||
public static final String APPWIDGET_DELETED = "nodomain.freeyourgadget.gadgetbridge.APPWIDGET_DELETED";
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Widget.class);
|
||||
static BroadcastReceiver broadcastReceiver = null;
|
||||
|
||||
private GBDevice getSelectedDevice() {
|
||||
|
||||
Context context = GBApplication.getContext();
|
||||
|
||||
if (!(context instanceof GBApplication)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
GBApplication gbApp = (GBApplication) context;
|
||||
|
||||
return gbApp.getDeviceManager().getSelectedDevice();
|
||||
}
|
||||
|
||||
private int[] getSteps() {
|
||||
Context context = GBApplication.getContext();
|
||||
Calendar day = GregorianCalendar.getInstance();
|
||||
|
||||
if (!(context instanceof GBApplication)) {
|
||||
return new int[]{0, 0, 0};
|
||||
}
|
||||
DailyTotals ds = new DailyTotals();
|
||||
return ds.getDailyTotalsForAllDevices(day);
|
||||
}
|
||||
|
||||
private String getHM(long value) {
|
||||
return DateTimeUtils.formatDurationHoursMinutes(value, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
|
||||
int appWidgetId) {
|
||||
|
||||
GBDevice device = getSelectedDevice();
|
||||
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
|
||||
|
||||
//onclick refresh
|
||||
Intent intent = new Intent(context, Widget.class);
|
||||
intent.setAction(WIDGET_CLICK);
|
||||
PendingIntent refreshDataIntent = PendingIntent.getBroadcast(
|
||||
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
//views.setOnClickPendingIntent(R.id.todaywidget_bottom_layout, refreshDataIntent);
|
||||
views.setOnClickPendingIntent(R.id.todaywidget_header_bar, refreshDataIntent);
|
||||
|
||||
//open GB main window
|
||||
Intent startMainIntent = new Intent(context, ControlCenterv2.class);
|
||||
PendingIntent startMainPIntent = PendingIntent.getActivity(context, 0, startMainIntent, 0);
|
||||
views.setOnClickPendingIntent(R.id.todaywidget_header_icon, startMainPIntent);
|
||||
|
||||
//alarms popup menu
|
||||
Intent startAlarmListIntent = new Intent(context, WidgetAlarmsActivity.class);
|
||||
PendingIntent startAlarmListPIntent = PendingIntent.getActivity(context, 0, startAlarmListIntent, 0);
|
||||
views.setOnClickPendingIntent(R.id.todaywidget_header_plus, startAlarmListPIntent);
|
||||
|
||||
//charts, requires device
|
||||
if (device != null) {
|
||||
Intent startChartsIntent = new Intent(context, ChartsActivity.class);
|
||||
startChartsIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
PendingIntent startChartsPIntent = PendingIntent.getActivity(context, 0, startChartsIntent, 0);
|
||||
views.setOnClickPendingIntent(R.id.todaywidget_bottom_layout, startChartsPIntent);
|
||||
}
|
||||
|
||||
|
||||
int[] DailyTotals = getSteps();
|
||||
|
||||
views.setTextViewText(R.id.todaywidget_steps, context.getString(R.string.widget_steps_label, (int) DailyTotals[0]));
|
||||
views.setTextViewText(R.id.todaywidget_sleep, context.getString(R.string.widget_sleep_label, getHM((long) DailyTotals[1])));
|
||||
|
||||
if (device != null) {
|
||||
String status = String.format("%1s", device.getStateString());
|
||||
if (device.isConnected()) {
|
||||
if (device.getBatteryLevel() > 1) {
|
||||
status = String.format("Battery %1s%%", device.getBatteryLevel());
|
||||
}
|
||||
}
|
||||
|
||||
views.setTextViewText(R.id.todaywidget_device_status, status);
|
||||
views.setTextViewText(R.id.todaywidget_device_name, device.getName());
|
||||
|
||||
}
|
||||
|
||||
// Instruct the widget manager to update the widget
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views);
|
||||
}
|
||||
|
||||
public void refreshData() {
|
||||
Context context = GBApplication.getContext();
|
||||
GBDevice device = getSelectedDevice();
|
||||
|
||||
if (device == null || !device.isInitialized()) {
|
||||
GB.toast(context,
|
||||
context.getString(R.string.device_not_connected),
|
||||
Toast.LENGTH_SHORT, GB.ERROR);
|
||||
GBApplication.deviceService().connect();
|
||||
GB.toast(context,
|
||||
context.getString(R.string.connecting),
|
||||
Toast.LENGTH_SHORT, GB.INFO);
|
||||
|
||||
return;
|
||||
}
|
||||
GB.toast(context,
|
||||
context.getString(R.string.busy_task_fetch_activity_data),
|
||||
Toast.LENGTH_SHORT, GB.INFO);
|
||||
|
||||
GBApplication.deviceService().onFetchRecordedData(RecordedDataTypes.TYPE_ACTIVITY);
|
||||
}
|
||||
|
||||
public void updateWidget() {
|
||||
Context context = GBApplication.getContext();
|
||||
|
||||
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
|
||||
ComponentName thisAppWidget = new ComponentName(context.getPackageName(), Widget.class.getName());
|
||||
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget);
|
||||
|
||||
onUpdate(context, appWidgetManager, appWidgetIds);
|
||||
|
||||
}
|
||||
|
||||
@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) {
|
||||
if (broadcastReceiver == null) {
|
||||
LOG.debug("gbwidget BROADCAST receiver initialized.");
|
||||
broadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
LOG.debug("gbwidget BROADCAST, action" + intent.getAction());
|
||||
updateWidget();
|
||||
}
|
||||
};
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(GBApplication.ACTION_NEW_DATA);
|
||||
intentFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
LocalBroadcastManager.getInstance(context).registerReceiver(broadcastReceiver, intentFilter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisabled(Context context) {
|
||||
|
||||
if (broadcastReceiver != null) {
|
||||
AndroidUtils.safeUnregisterBroadcastReceiver(context,broadcastReceiver);
|
||||
broadcastReceiver = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
super.onReceive(context, intent);
|
||||
LOG.debug("gbwidget LOCAL onReceive, action: " + intent.getAction());
|
||||
//this handles widget re-connection after apk updates
|
||||
if (WIDGET_CLICK.equals(intent.getAction())) {
|
||||
if (broadcastReceiver == null) {
|
||||
onEnabled(context);
|
||||
}
|
||||
refreshData();
|
||||
//updateWidget();
|
||||
} else if (APPWIDGET_DELETED.equals(intent.getAction())) {
|
||||
onDisabled(context);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2019 Carsten Pfeiffer, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -16,13 +16,14 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
|
||||
public abstract class AbstractFragmentPagerAdapter extends FragmentStatePagerAdapter {
|
||||
private final Set<AbstractGBFragment> fragments = new HashSet<>();
|
||||
private Object primaryFragment;
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Lem Dulfo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -22,11 +23,11 @@ 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 androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, walkjivefly
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
@ -17,8 +17,8 @@
|
||||
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 androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
/**
|
||||
* Abstract base class for fragments. Provides hooks that are called when
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -17,8 +18,11 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
|
||||
/**
|
||||
* A base activity that supports paging through fragments by swiping.
|
||||
@ -35,12 +39,12 @@ import android.support.v4.app.FragmentPagerAdapter;
|
||||
*/
|
||||
public abstract class AbstractGBFragmentActivity extends AbstractGBActivity {
|
||||
/**
|
||||
* The {@link android.support.v4.view.PagerAdapter} that will provide
|
||||
* The {@link PagerAdapter} that will provide
|
||||
* fragments for each of the sections. We use a
|
||||
* {@link FragmentPagerAdapter} derivative, which will keep every
|
||||
* loaded fragment in memory. If this becomes too memory intensive, it
|
||||
* may be best to switch to a
|
||||
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
|
||||
* {@link FragmentStatePagerAdapter}.
|
||||
*/
|
||||
private AbstractFragmentPagerAdapter mSectionsPagerAdapter;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017-2018 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2017-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Christian
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Christian
|
||||
Fischer, Daniele Gobbetti, Lem Dulfo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
@ -26,8 +26,6 @@ import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.InputType;
|
||||
import android.view.MenuItem;
|
||||
|
||||
@ -36,6 +34,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
/* Copyright (C) 2017-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
@ -25,10 +25,6 @@ import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
@ -40,6 +36,12 @@ import android.widget.DatePicker;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@ -239,10 +241,10 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
||||
date.set(year, monthOfYear, dayOfMonth);
|
||||
|
||||
long timestamp = date.getTimeInMillis() - 1000;
|
||||
SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
|
||||
editor.remove(mGBDevice.getAddress() + "_" + "lastSportsActivityTimeMillis"); //FIXME: key reconstruction is BAD
|
||||
editor.putLong(mGBDevice.getAddress() + "_" + "lastSportsActivityTimeMillis", timestamp);
|
||||
editor.commit();
|
||||
SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress()).edit();
|
||||
editor.remove("lastSportsActivityTimeMillis"); //FIXME: key reconstruction is BAD
|
||||
editor.putLong("lastSportsActivityTimeMillis", timestamp);
|
||||
editor.apply();
|
||||
}
|
||||
}, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Lem Dulfo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
@ -26,14 +26,16 @@ import android.widget.TimePicker;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
public class AlarmDetails extends AbstractGBActivity {
|
||||
|
||||
private GBAlarm alarm;
|
||||
private Alarm alarm;
|
||||
private TimePicker timePicker;
|
||||
private CheckedTextView cbSmartWakeup;
|
||||
private CheckedTextView cbMonday;
|
||||
@ -50,7 +52,7 @@ public class AlarmDetails extends AbstractGBActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_alarm_details);
|
||||
|
||||
alarm = getIntent().getParcelableExtra("alarm");
|
||||
alarm = (Alarm) getIntent().getSerializableExtra(nodomain.freeyourgadget.gadgetbridge.model.Alarm.EXTRA_ALARM);
|
||||
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
|
||||
timePicker = findViewById(R.id.alarm_time_picker);
|
||||
@ -109,17 +111,17 @@ public class AlarmDetails extends AbstractGBActivity {
|
||||
timePicker.setCurrentHour(alarm.getHour());
|
||||
timePicker.setCurrentMinute(alarm.getMinute());
|
||||
|
||||
cbSmartWakeup.setChecked(alarm.isSmartWakeup());
|
||||
cbSmartWakeup.setChecked(alarm.getSmartWakeup());
|
||||
int smartAlarmVisibility = supportsSmartWakeup() ? View.VISIBLE : View.GONE;
|
||||
cbSmartWakeup.setVisibility(smartAlarmVisibility);
|
||||
|
||||
cbMonday.setChecked(alarm.getRepetition(GBAlarm.ALARM_MON));
|
||||
cbTuesday.setChecked(alarm.getRepetition(GBAlarm.ALARM_TUE));
|
||||
cbWednesday.setChecked(alarm.getRepetition(GBAlarm.ALARM_WED));
|
||||
cbThursday.setChecked(alarm.getRepetition(GBAlarm.ALARM_THU));
|
||||
cbFriday.setChecked(alarm.getRepetition(GBAlarm.ALARM_FRI));
|
||||
cbSaturday.setChecked(alarm.getRepetition(GBAlarm.ALARM_SAT));
|
||||
cbSunday.setChecked(alarm.getRepetition(GBAlarm.ALARM_SUN));
|
||||
cbMonday.setChecked(alarm.getRepetition(Alarm.ALARM_MON));
|
||||
cbTuesday.setChecked(alarm.getRepetition(Alarm.ALARM_TUE));
|
||||
cbWednesday.setChecked(alarm.getRepetition(Alarm.ALARM_WED));
|
||||
cbThursday.setChecked(alarm.getRepetition(Alarm.ALARM_THU));
|
||||
cbFriday.setChecked(alarm.getRepetition(Alarm.ALARM_FRI));
|
||||
cbSaturday.setChecked(alarm.getRepetition(Alarm.ALARM_SAT));
|
||||
cbSunday.setChecked(alarm.getRepetition(Alarm.ALARM_SUN));
|
||||
|
||||
}
|
||||
|
||||
@ -144,10 +146,11 @@ public class AlarmDetails extends AbstractGBActivity {
|
||||
|
||||
private void updateAlarm() {
|
||||
alarm.setSmartWakeup(supportsSmartWakeup() && cbSmartWakeup.isChecked());
|
||||
alarm.setRepetition(cbMonday.isChecked(), cbTuesday.isChecked(), cbWednesday.isChecked(), cbThursday.isChecked(), cbFriday.isChecked(), cbSaturday.isChecked(), cbSunday.isChecked());
|
||||
int repetitionMask = AlarmUtils.createRepetitionMassk(cbMonday.isChecked(), cbTuesday.isChecked(), cbWednesday.isChecked(), cbThursday.isChecked(), cbFriday.isChecked(), cbSaturday.isChecked(), cbSunday.isChecked());
|
||||
alarm.setRepetition(repetitionMask);
|
||||
alarm.setHour(timePicker.getCurrentHour());
|
||||
alarm.setMinute(timePicker.getCurrentMinute());
|
||||
alarm.store();
|
||||
DBHelper.store(alarm);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Lem Dulfo
|
||||
/* Copyright (C) 2015-2019 abettenburg, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Lem Dulfo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -18,10 +18,6 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@ -29,6 +25,10 @@ import android.view.MenuItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.AppBlacklistAdapter;
|
||||
|
||||
|
@ -19,15 +19,16 @@ 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;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
/**
|
||||
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
|
||||
* to be used with AppCompat.
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017-2018 Carsten Pfeiffer, Daniele Gobbetti
|
||||
/* Copyright (C) 2017-2019 Carsten Pfeiffer, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -24,10 +24,6 @@ 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;
|
||||
@ -42,6 +38,10 @@ import android.widget.Toast;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NavUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Lem Dulfo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
@ -17,35 +17,47 @@
|
||||
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.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
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.GBAlarmListAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ALARMS;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
|
||||
public class ConfigureAlarms extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConfigureAlarms.class);
|
||||
|
||||
private static final int REQ_CONFIGURE_ALARM = 1;
|
||||
|
||||
private GBAlarmListAdapter mGBAlarmListAdapter;
|
||||
private Set<String> preferencesAlarmListSet;
|
||||
private boolean avoidSendAlarmsToDevice;
|
||||
private GBDevice device;
|
||||
private GBDevice gbDevice;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -53,28 +65,24 @@ public class ConfigureAlarms extends AbstractGBActivity {
|
||||
|
||||
setContentView(R.layout.activity_configure_alarms);
|
||||
|
||||
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(DeviceService.ACTION_SAVE_ALARMS);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
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));
|
||||
prefs.getPreferences().edit().putStringSet(PREF_MIBAND_ALARMS, preferencesAlarmListSet).apply();
|
||||
}
|
||||
gbDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
|
||||
mGBAlarmListAdapter = new GBAlarmListAdapter(this, preferencesAlarmListSet);
|
||||
mGBAlarmListAdapter = new GBAlarmListAdapter(this);
|
||||
|
||||
RecyclerView alarmsRecyclerView = (RecyclerView) findViewById(R.id.alarm_list);
|
||||
RecyclerView alarmsRecyclerView = findViewById(R.id.alarm_list);
|
||||
alarmsRecyclerView.setHasFixedSize(true);
|
||||
alarmsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
alarmsRecyclerView.setAdapter(mGBAlarmListAdapter);
|
||||
updateAlarmsFromPrefs();
|
||||
updateAlarmsFromDB();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
if (!avoidSendAlarmsToDevice) {
|
||||
if (!avoidSendAlarmsToDevice && gbDevice.isInitialized()) {
|
||||
sendAlarmsToDevice();
|
||||
}
|
||||
super.onPause();
|
||||
@ -82,21 +90,65 @@ public class ConfigureAlarms extends AbstractGBActivity {
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQ_CONFIGURE_ALARM) {
|
||||
avoidSendAlarmsToDevice = false;
|
||||
updateAlarmsFromPrefs();
|
||||
updateAlarmsFromDB();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
/**
|
||||
* Reads the available alarms from the database and updates the view afterwards.
|
||||
*/
|
||||
private void updateAlarmsFromDB() {
|
||||
List<Alarm> alarms = DBHelper.getAlarms(getGbDevice());
|
||||
if (alarms.isEmpty()) {
|
||||
alarms = AlarmUtils.readAlarmsFromPrefs(getGbDevice());
|
||||
storeMigratedAlarms(alarms);
|
||||
}
|
||||
addMissingAlarms(alarms);
|
||||
|
||||
mGBAlarmListAdapter.setAlarmList(preferencesAlarmListSet, reservedSlots);
|
||||
mGBAlarmListAdapter.setAlarmList(alarms);
|
||||
mGBAlarmListAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void storeMigratedAlarms(List<Alarm> alarms) {
|
||||
for (Alarm alarm : alarms) {
|
||||
DBHelper.store(alarm);
|
||||
}
|
||||
}
|
||||
|
||||
private void addMissingAlarms(List<Alarm> alarms) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(getGbDevice());
|
||||
int supportedNumAlarms = coordinator.getAlarmSlotCount();
|
||||
if (supportedNumAlarms > alarms.size()) {
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
DaoSession daoSession = db.getDaoSession();
|
||||
Device device = DBHelper.getDevice(getGbDevice(), daoSession);
|
||||
User user = DBHelper.getUser(daoSession);
|
||||
for (int position = 0; position < supportedNumAlarms; position++) {
|
||||
boolean found = false;
|
||||
for (Alarm alarm : alarms) {
|
||||
if (alarm.getPosition() == position) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
LOG.info("adding missing alarm at position " + position);
|
||||
alarms.add(position, createDefaultAlarm(device, user, position));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error accessing database", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Alarm createDefaultAlarm(@NonNull Device device, @NonNull User user, int position) {
|
||||
return new Alarm(device.getId(), user.getId(), position, false, false, 0, 6, 30, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
@ -108,19 +160,40 @@ public class ConfigureAlarms extends AbstractGBActivity {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public void configureAlarm(GBAlarm alarm) {
|
||||
public void configureAlarm(Alarm alarm) {
|
||||
avoidSendAlarmsToDevice = true;
|
||||
Intent startIntent = new Intent(getApplicationContext(), AlarmDetails.class);
|
||||
startIntent.putExtra("alarm", alarm);
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, getDevice());
|
||||
startIntent.putExtra(Alarm.EXTRA_ALARM, alarm);
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, getGbDevice());
|
||||
startActivityForResult(startIntent, REQ_CONFIGURE_ALARM);
|
||||
}
|
||||
|
||||
private GBDevice getDevice() {
|
||||
return device;
|
||||
private GBDevice getGbDevice() {
|
||||
return gbDevice;
|
||||
}
|
||||
|
||||
private void sendAlarmsToDevice() {
|
||||
GBApplication.deviceService().onSetAlarms(mGBAlarmListAdapter.getAlarmList());
|
||||
}
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
|
||||
switch (action) {
|
||||
case DeviceService.ACTION_SAVE_ALARMS: {
|
||||
updateAlarmsFromDB();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Taavi Eomäe
|
||||
/* Copyright (C) 2016-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Johannes Tysiak, Taavi Eomäe, vanous
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -27,22 +27,27 @@ import android.content.pm.PackageManager;
|
||||
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.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.GravityCompat;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -72,9 +77,14 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
|
||||
private GBDeviceAdapterv2 mGBDeviceAdapter;
|
||||
private RecyclerView deviceListView;
|
||||
private FloatingActionButton fab;
|
||||
|
||||
private boolean isLanguageInvalid = false;
|
||||
|
||||
public static final int MENU_REFRESH_CODE=1;
|
||||
|
||||
private static PhoneStateListener fakeStateListener;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
@ -102,14 +112,6 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
FloatingActionButton fab = findViewById(R.id.fab);
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
launchDiscoveryActivity();
|
||||
}
|
||||
});
|
||||
|
||||
DrawerLayout drawer = findViewById(R.id.drawer_layout);
|
||||
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
|
||||
this, drawer, toolbar, R.string.controlcenter_navigation_drawer_open, R.string.controlcenter_navigation_drawer_close);
|
||||
@ -131,6 +133,16 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
|
||||
deviceListView.setAdapter(this.mGBDeviceAdapter);
|
||||
|
||||
fab = findViewById(R.id.fab);
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
launchDiscoveryActivity();
|
||||
}
|
||||
});
|
||||
|
||||
showFabIfNeccessary();
|
||||
|
||||
/* uncomment to enable fixed-swipe to reveal more actions
|
||||
|
||||
ItemTouchHelper swipeToDismissTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
|
||||
@ -191,7 +203,12 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
|
||||
ChangeLog cl = createChangeLog();
|
||||
if (cl.isFirstRun()) {
|
||||
cl.getLogDialog().show();
|
||||
try {
|
||||
cl.getLogDialog().show();
|
||||
} catch (Exception ignored){
|
||||
GB.toast(getBaseContext(), "Error showing Changelog", Toast.LENGTH_LONG, GB.ERROR);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
GBApplication.deviceService().start();
|
||||
@ -229,6 +246,15 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == MENU_REFRESH_CODE) {
|
||||
showFabIfNeccessary();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
||||
|
||||
@ -238,7 +264,7 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_settings:
|
||||
Intent settingsIntent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(settingsIntent);
|
||||
startActivityForResult(settingsIntent, MENU_REFRESH_CODE);
|
||||
return true;
|
||||
case R.id.action_debug:
|
||||
Intent debugIntent = new Intent(this, DebugActivity.class);
|
||||
@ -252,6 +278,9 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
Intent blIntent = new Intent(this, AppBlacklistActivity.class);
|
||||
startActivity(blIntent);
|
||||
return true;
|
||||
case R.id.device_action_discover:
|
||||
launchDiscoveryActivity();
|
||||
return true;
|
||||
case R.id.action_quit:
|
||||
GBApplication.quit();
|
||||
return true;
|
||||
@ -262,7 +291,11 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
return true;
|
||||
case R.id.external_changelog:
|
||||
ChangeLog cl = createChangeLog();
|
||||
cl.getFullLogDialog().show();
|
||||
try {
|
||||
cl.getLogDialog().show();
|
||||
} catch (Exception ignored) {
|
||||
GB.toast(getBaseContext(), "Error showing Changelog", Toast.LENGTH_LONG, GB.ERROR);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -276,7 +309,8 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
+ "background-color: " + AndroidUtils.getBackgroundColorHex(getBaseContext()) + ";" +
|
||||
"}";
|
||||
return new ChangeLog(this, css);
|
||||
}
|
||||
}
|
||||
|
||||
private void launchDiscoveryActivity() {
|
||||
startActivity(new Intent(this, DiscoveryActivity.class));
|
||||
}
|
||||
@ -285,6 +319,18 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
mGBDeviceAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void showFabIfNeccessary() {
|
||||
if (GBApplication.getPrefs().getBoolean("display_add_device_fab", true)) {
|
||||
fab.show();
|
||||
} else {
|
||||
if (deviceListView.getChildCount() < 1) {
|
||||
fab.show();
|
||||
} else {
|
||||
fab.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private void checkAndRequestPermissions() {
|
||||
List<String> wantedPermissions = new ArrayList<>();
|
||||
@ -320,7 +366,15 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
}
|
||||
|
||||
if (!wantedPermissions.isEmpty())
|
||||
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[wantedPermissions.size()]), 0);
|
||||
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0);
|
||||
|
||||
// HACK: On Lineage we have to do this so that the permission dialog pops up
|
||||
if (fakeStateListener == null) {
|
||||
fakeStateListener = new PhoneStateListener();
|
||||
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
|
||||
telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||
telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setLanguage(Locale language, boolean invalidateLanguage) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2016-2018 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti
|
||||
/* Copyright (C) 2016-2019 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, vanous
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -17,61 +17,67 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.NavUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
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.database.PeriodicExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
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.ImportExportSharedPreferences;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
|
||||
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);
|
||||
TextView dbPath = findViewById(R.id.activity_db_management_path);
|
||||
dbPath.setText(getExternalPath());
|
||||
|
||||
exportDBButton = (Button) findViewById(R.id.exportDBButton);
|
||||
Button exportDBButton = findViewById(R.id.exportDBButton);
|
||||
exportDBButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
exportDB();
|
||||
}
|
||||
});
|
||||
importDBButton = (Button) findViewById(R.id.importDBButton);
|
||||
Button importDBButton = findViewById(R.id.importDBButton);
|
||||
importDBButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@ -81,7 +87,10 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
|
||||
int oldDBVisibility = hasOldActivityDatabase() ? View.VISIBLE : View.GONE;
|
||||
|
||||
deleteOldActivityDBButton = (Button) findViewById(R.id.deleteOldActivityDB);
|
||||
TextView deleteOldActivityTitle = findViewById(R.id.mergeOldActivityDataTitle);
|
||||
deleteOldActivityTitle.setVisibility(oldDBVisibility);
|
||||
|
||||
Button deleteOldActivityDBButton = findViewById(R.id.deleteOldActivityDB);
|
||||
deleteOldActivityDBButton.setVisibility(oldDBVisibility);
|
||||
deleteOldActivityDBButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -90,7 +99,7 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
}
|
||||
});
|
||||
|
||||
deleteDBButton = (Button) findViewById(R.id.emptyDBButton);
|
||||
Button deleteDBButton = findViewById(R.id.emptyDBButton);
|
||||
deleteDBButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@ -98,9 +107,68 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
}
|
||||
});
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
boolean autoExportEnabled = prefs.getBoolean(GBPrefs.AUTO_EXPORT_ENABLED, false);
|
||||
int autoExportInterval = prefs.getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
|
||||
//returns an ugly content://...
|
||||
//String autoExportLocation = prefs.getString(GBPrefs.AUTO_EXPORT_LOCATION, "");
|
||||
|
||||
int testExportVisibility = (autoExportInterval > 0 && autoExportEnabled) ? View.VISIBLE : View.GONE;
|
||||
|
||||
TextView autoExportLocation_label = findViewById(R.id.autoExportLocation_label);
|
||||
autoExportLocation_label.setVisibility(testExportVisibility);
|
||||
|
||||
TextView autoExportLocation_intro = findViewById(R.id.autoExportLocation_intro);
|
||||
autoExportLocation_intro.setVisibility(testExportVisibility);
|
||||
|
||||
TextView autoExportLocation_path = findViewById(R.id.autoExportLocation_path);
|
||||
autoExportLocation_path.setVisibility(testExportVisibility);
|
||||
autoExportLocation_path.setText(getAutoExportLocationSummary());
|
||||
|
||||
final Context context = getApplicationContext();
|
||||
Button testExportDBButton = findViewById(R.id.testExportDBButton);
|
||||
testExportDBButton.setVisibility(testExportVisibility);
|
||||
testExportDBButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
sendBroadcast(new Intent(context, PeriodicExporter.class));
|
||||
GB.toast(context,
|
||||
context.getString(R.string.activity_DB_test_export_message),
|
||||
Toast.LENGTH_SHORT, GB.INFO);
|
||||
}
|
||||
});
|
||||
|
||||
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
}
|
||||
|
||||
//would rather re-use method of SettingsActivity... but lifecycle...
|
||||
private String getAutoExportLocationSummary() {
|
||||
String autoExportLocation = GBApplication.getPrefs().getString(GBPrefs.AUTO_EXPORT_LOCATION, null);
|
||||
if (autoExportLocation == null) {
|
||||
return "";
|
||||
}
|
||||
Uri uri = Uri.parse(autoExportLocation);
|
||||
try {
|
||||
return AndroidUtils.getFilePath(getApplicationContext(), uri);
|
||||
} catch (IllegalArgumentException e) {
|
||||
try {
|
||||
Cursor cursor = getContentResolver().query(
|
||||
uri,
|
||||
new String[]{DocumentsContract.Document.COLUMN_DISPLAY_NAME},
|
||||
null, null, null, null
|
||||
);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
|
||||
}
|
||||
}
|
||||
catch (Exception fdfsdfds) {
|
||||
LOG.warn("fuck");
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
private boolean hasOldActivityDatabase() {
|
||||
return new DBHelper(this).existsDB("ActivityDatabase");
|
||||
}
|
||||
@ -115,27 +183,57 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
}
|
||||
|
||||
private void exportShared() {
|
||||
// BEGIN EXAMPLE
|
||||
File myPath = null;
|
||||
try {
|
||||
myPath = FileUtils.getExternalFilesDir();
|
||||
File myPath = FileUtils.getExternalFilesDir();
|
||||
File myFile = new File(myPath, "Export_preference");
|
||||
shared_file.exportToFile(sharedPrefs,myFile,null);
|
||||
ImportExportSharedPreferences.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);
|
||||
}
|
||||
try (DBHandler lockHandler = GBApplication.acquireDB()) {
|
||||
List<Device> activeDevices = DBHelper.getActiveDevices(lockHandler.getDaoSession());
|
||||
for (Device dbDevice : activeDevices) {
|
||||
SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
|
||||
if (sharedPrefs != null) {
|
||||
File myPath = FileUtils.getExternalFilesDir();
|
||||
File myFile = new File(myPath, "Export_preference_" + dbDevice.getIdentifier());
|
||||
try {
|
||||
ImportExportSharedPreferences.exportToFile(deviceSharedPrefs, myFile, null);
|
||||
} catch (Exception ignore) {
|
||||
// some devices no not have device specific preferences
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
GB.toast("Error exporting device specific preferences", Toast.LENGTH_SHORT, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void importShared() {
|
||||
// BEGIN EXAMPLE
|
||||
File myPath = null;
|
||||
try {
|
||||
myPath = FileUtils.getExternalFilesDir();
|
||||
File myPath = FileUtils.getExternalFilesDir();
|
||||
File myFile = new File(myPath, "Export_preference");
|
||||
shared_file.importFromFile(sharedPrefs,myFile );
|
||||
ImportExportSharedPreferences.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);
|
||||
}
|
||||
try (DBHandler lockHandler = GBApplication.acquireDB()) {
|
||||
List<Device> activeDevices = DBHelper.getActiveDevices(lockHandler.getDaoSession());
|
||||
for (Device dbDevice : activeDevices) {
|
||||
SharedPreferences deviceSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
|
||||
if (sharedPrefs != null) {
|
||||
File myPath = FileUtils.getExternalFilesDir();
|
||||
File myFile = new File(myPath, "Export_preference_" + dbDevice.getIdentifier());
|
||||
try {
|
||||
ImportExportSharedPreferences.importFromFile(deviceSharedPrefs, myFile);
|
||||
} catch (Exception ignore) {
|
||||
// some devices no not have device specific preferences
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
GB.toast("Error importing device specific preferences", Toast.LENGTH_SHORT, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void exportDB() {
|
||||
@ -159,7 +257,6 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
@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();
|
||||
@ -170,6 +267,7 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
} catch (Exception ex) {
|
||||
GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_error_importing_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
importShared();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
|
||||
@ -227,10 +325,9 @@ public class DbManagementActivity extends AbstractGBActivity {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Frank Slezak, ivanovlev, Kasha, Lem Dulfo, Pavel Elagin, Steffen
|
||||
Liebergeld
|
||||
Liebergeld, vanous
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
@ -26,29 +27,36 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
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.DatePicker;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.RemoteInput;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
@ -56,6 +64,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
import static android.content.Intent.EXTRA_SUBJECT;
|
||||
@ -67,10 +76,6 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
private static final String EXTRA_REPLY = "reply";
|
||||
private static final String ACTION_REPLY
|
||||
= "nodomain.freeyourgadget.gadgetbridge.DebugActivity.action.reply";
|
||||
|
||||
private Spinner sendTypeSpinner;
|
||||
|
||||
private EditText editContent;
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
@ -82,12 +87,24 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
GB.toast(context, "got wearable reply: " + reply, Toast.LENGTH_SHORT, GB.INFO);
|
||||
break;
|
||||
}
|
||||
case DeviceService.ACTION_REALTIME_SAMPLES:
|
||||
handleRealtimeSample(intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE));
|
||||
break;
|
||||
default:
|
||||
LOG.info("ignoring intent action " + intent.getAction());
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
private Spinner sendTypeSpinner;
|
||||
private EditText editContent;
|
||||
|
||||
private void handleRealtimeSample(Serializable extra) {
|
||||
if (extra instanceof ActivitySample) {
|
||||
ActivitySample sample = (ActivitySample) extra;
|
||||
GB.toast(this, "Heart Rate measured: " + sample.getHeartRate(), Toast.LENGTH_LONG, GB.INFO);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -96,7 +113,7 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_REPLY);
|
||||
filter.addAction(DeviceService.ACTION_HEARTRATE_MEASUREMENT);
|
||||
filter.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
registerReceiver(mReceiver, filter); // for ACTION_REPLY
|
||||
|
||||
@ -122,7 +139,6 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
notificationSpec.subject = testString;
|
||||
notificationSpec.type = NotificationType.values()[sendTypeSpinner.getSelectedItemPosition()];
|
||||
notificationSpec.pebbleColor = notificationSpec.type.color;
|
||||
notificationSpec.id = -1;
|
||||
GBApplication.deviceService().onNotification(notificationSpec);
|
||||
}
|
||||
});
|
||||
@ -171,9 +187,33 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
rebootButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
GBApplication.deviceService().onReboot();
|
||||
GBApplication.deviceService().onReset(GBDeviceProtocol.RESET_FLAGS_REBOOT);
|
||||
}
|
||||
});
|
||||
|
||||
Button factoryResetButton = findViewById(R.id.factoryResetButton);
|
||||
factoryResetButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new AlertDialog.Builder(DebugActivity.this)
|
||||
.setCancelable(true)
|
||||
.setTitle(R.string.debugactivity_really_factoryreset_title)
|
||||
.setMessage(R.string.debugactivity_really_factoryreset)
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
GBApplication.deviceService().onReset(GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
});
|
||||
|
||||
Button heartRateButton = findViewById(R.id.HeartRateButton);
|
||||
heartRateButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -183,6 +223,43 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
}
|
||||
});
|
||||
|
||||
Button setFetchTimeButton = findViewById(R.id.SetFetchTimeButton);
|
||||
setFetchTimeButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
final Calendar currentDate = Calendar.getInstance();
|
||||
Context context = getApplicationContext();
|
||||
|
||||
if (context instanceof GBApplication) {
|
||||
GBApplication gbApp = (GBApplication) context;
|
||||
final GBDevice device = gbApp.getDeviceManager().getSelectedDevice();
|
||||
if (device != null) {
|
||||
new DatePickerDialog(DebugActivity.this, new DatePickerDialog.OnDateSetListener() {
|
||||
@Override
|
||||
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
|
||||
Calendar date = Calendar.getInstance();
|
||||
date.set(year, monthOfYear, dayOfMonth);
|
||||
|
||||
long timestamp = date.getTimeInMillis() - 1000;
|
||||
GB.toast("Setting lastSyncTimeMillis: " + timestamp, Toast.LENGTH_LONG, GB.INFO);
|
||||
|
||||
SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).edit();
|
||||
editor.remove("lastSyncTimeMillis"); //FIXME: key reconstruction is BAD
|
||||
editor.putLong("lastSyncTimeMillis", timestamp);
|
||||
editor.apply();
|
||||
}
|
||||
}, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
|
||||
} else {
|
||||
GB.toast("Device not selected/connected", Toast.LENGTH_LONG, GB.INFO);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Button setMusicInfoButton = findViewById(R.id.setMusicInfoButton);
|
||||
setMusicInfoButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -225,6 +302,14 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
}
|
||||
});
|
||||
|
||||
Button testPebbleKitNotificationButton = findViewById(R.id.testPebbleKitNotificationButton);
|
||||
testPebbleKitNotificationButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
testPebbleKitNotification();
|
||||
}
|
||||
});
|
||||
|
||||
Button fetchDebugLogsButton = findViewById(R.id.fetchDebugLogsButton);
|
||||
fetchDebugLogsButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -258,14 +343,7 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String fileName = GBApplication.getLogPath();
|
||||
if (fileName != null && fileName.length() > 0) {
|
||||
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
|
||||
emailIntent.setType("*/*");
|
||||
emailIntent.putExtra(EXTRA_SUBJECT, "Gadgetbridge log file");
|
||||
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(fileName)));
|
||||
startActivity(Intent.createChooser(emailIntent, "Share File"));
|
||||
}
|
||||
shareLog();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
|
||||
@ -283,11 +361,17 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
|
||||
private void shareLog() {
|
||||
String fileName = GBApplication.getLogPath();
|
||||
if(fileName != null && fileName.length() > 0) {
|
||||
if (fileName != null && fileName.length() > 0) {
|
||||
File logFile = new File(fileName);
|
||||
if (!logFile.exists()) {
|
||||
GB.toast("File does not exist", Toast.LENGTH_LONG, GB.INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
|
||||
emailIntent.setType("*/*");
|
||||
emailIntent.putExtra(EXTRA_SUBJECT, "Gadgetbridge log file");
|
||||
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(fileName)));
|
||||
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(logFile));
|
||||
startActivity(Intent.createChooser(emailIntent, "Share File"));
|
||||
}
|
||||
}
|
||||
@ -329,6 +413,13 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void testPebbleKitNotification() {
|
||||
Intent pebbleKitIntent = new Intent("com.getpebble.action.SEND_NOTIFICATION");
|
||||
pebbleKitIntent.putExtra("messageType", "PEBBLE_ALERT");
|
||||
pebbleKitIntent.putExtra("notificationData", "[{\"title\":\"PebbleKitTest\",\"body\":\"sent from Gadgetbridge\"}]");
|
||||
getApplicationContext().sendBroadcast(pebbleKitIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, boun, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, JohnnySun, jonnsoft, Lem Dulfo, Taavi Eomäe, Uwe Hermann
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, boun, Carsten Pfeiffer, Daniel
|
||||
Dakhno, Daniele Gobbetti, JohnnySun, jonnsoft, Lem Dulfo, Taavi Eomäe,
|
||||
Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -35,6 +36,7 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@ -42,7 +44,6 @@ 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;
|
||||
@ -50,6 +51,9 @@ import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -59,6 +63,7 @@ import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.DeviceCandidateAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
@ -69,12 +74,15 @@ import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
|
||||
public class DiscoveryActivity extends AbstractGBActivity implements AdapterView.OnItemClickListener {
|
||||
public class DiscoveryActivity extends AbstractGBActivity implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DiscoveryActivity.class);
|
||||
private static final long SCAN_DURATION = 60000; // 60s
|
||||
|
||||
private ScanCallback newLeScanCallback = null;
|
||||
|
||||
// Disabled for testing, it seems worse for a few people
|
||||
private boolean disableNewBLEScanning = false;
|
||||
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
|
||||
@ -93,7 +101,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
// continue with LE scan, if available
|
||||
if (isScanning == Scanning.SCANNING_BT) {
|
||||
checkAndRequestLocationPermission();
|
||||
if (GBApplication.isRunningLollipopOrLater()) {
|
||||
if (GBApplication.isRunningLollipopOrLater() && !disableNewBLEScanning) {
|
||||
startDiscovery(Scanning.SCANNING_NEW_BTLE);
|
||||
} else {
|
||||
startDiscovery(Scanning.SCANNING_BTLE);
|
||||
@ -189,8 +197,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
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);
|
||||
//logMessageContent(scanRecord);
|
||||
handleDeviceFound(device, (short) rssi);
|
||||
}
|
||||
};
|
||||
@ -261,6 +268,11 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
disableNewBLEScanning = GBApplication.getPrefs().getBoolean("disable_new_ble_scanning", false);
|
||||
if (disableNewBLEScanning) {
|
||||
LOG.info("new BLE scanning disabled via settings, using old method");
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_discovery);
|
||||
startButton = findViewById(R.id.discovery_start);
|
||||
startButton.setOnClickListener(new View.OnClickListener() {
|
||||
@ -279,6 +291,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
cadidateListAdapter = new DeviceCandidateAdapter(this, deviceCandidates);
|
||||
deviceCandidatesView.setAdapter(cadidateListAdapter);
|
||||
deviceCandidatesView.setOnItemClickListener(this);
|
||||
deviceCandidatesView.setOnItemLongClickListener(this);
|
||||
|
||||
IntentFilter bluetoothIntents = new IntentFilter();
|
||||
bluetoothIntents.addAction(BluetoothDevice.ACTION_FOUND);
|
||||
@ -294,7 +307,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelableArrayList("deviceCandidates", deviceCandidates);
|
||||
}
|
||||
@ -331,6 +344,12 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
}
|
||||
|
||||
private void handleDeviceFound(BluetoothDevice device, short rssi) {
|
||||
if (device.getName() != null) {
|
||||
if (handleDeviceFound(device,rssi, null)) {
|
||||
LOG.info("found supported device " + device.getName() + " without scanning services, skipping service scan.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
ParcelUuid[] uuids = device.getUuids();
|
||||
if (uuids == null) {
|
||||
if (device.fetchUuidsWithSdp()) {
|
||||
@ -342,7 +361,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
}
|
||||
|
||||
|
||||
private void handleDeviceFound(BluetoothDevice device, short rssi, ParcelUuid[] uuids) {
|
||||
private boolean handleDeviceFound(BluetoothDevice device, short rssi, ParcelUuid[] uuids) {
|
||||
LOG.debug("found device: " + device.getName() + ", " + device.getAddress());
|
||||
if (LOG.isDebugEnabled()) {
|
||||
if (uuids != null && uuids.length > 0) {
|
||||
@ -352,7 +371,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
}
|
||||
}
|
||||
if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
|
||||
return; // ignore already bonded devices
|
||||
return true; // ignore already bonded devices
|
||||
}
|
||||
|
||||
GBDeviceCandidate candidate = new GBDeviceCandidate(device, rssi, uuids);
|
||||
@ -367,7 +386,9 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
deviceCandidates.add(candidate);
|
||||
}
|
||||
cadidateListAdapter.notifyDataSetChanged();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -577,6 +598,27 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
return m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
GBDeviceCandidate deviceCandidate = deviceCandidates.get(position);
|
||||
if (deviceCandidate == null) {
|
||||
LOG.error("Device candidate clicked, but item not found");
|
||||
return true;
|
||||
}
|
||||
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(deviceCandidate);
|
||||
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
|
||||
if (coordinator.getSupportedDeviceSpecificSettings(device) == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Intent startIntent;
|
||||
startIntent = new Intent(this, DeviceSettingsActivity.class);
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
startActivity(startIntent);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
GBDeviceCandidate deviceCandidate = deviceCandidates.get(position);
|
||||
@ -588,6 +630,17 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
stopDiscovery();
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(deviceCandidate);
|
||||
LOG.info("Using device candidate " + deviceCandidate + " with coordinator: " + coordinator.getClass());
|
||||
|
||||
if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_REQUIRE_KEY) {
|
||||
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceCandidate.getMacAddress());
|
||||
|
||||
String authKey = sharedPrefs.getString("authkey", null);
|
||||
if (authKey == null || authKey.isEmpty() || authKey.getBytes().length < 34 || !authKey.substring(0, 2).equals("0x")) {
|
||||
GB.toast(DiscoveryActivity.this, getString(R.string.discovery_need_to_enter_authkey), Toast.LENGTH_LONG, GB.WARN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Class<? extends Activity> pairingActivity = coordinator.getPairingActivity();
|
||||
if (pairingActivity != null) {
|
||||
Intent intent = new Intent(this, pairingActivity);
|
||||
@ -595,7 +648,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
startActivity(intent);
|
||||
} else {
|
||||
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
|
||||
int bondingStyle = coordinator.getBondingStyle(device);
|
||||
int bondingStyle = coordinator.getBondingStyle();
|
||||
if (bondingStyle == DeviceCoordinator.BONDING_STYLE_NONE) {
|
||||
LOG.info("No bonding needed, according to coordinator, so connecting right away");
|
||||
connectAndFinish(device);
|
||||
@ -628,7 +681,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
||||
super.onPause();
|
||||
stopBTDiscovery();
|
||||
stopBTLEDiscovery();
|
||||
if (GBApplication.isRunningLollipopOrLater()) {
|
||||
if (GBApplication.isRunningLollipopOrLater() && !disableNewBLEScanning) {
|
||||
stopNewBTLEDiscovery();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
/* Copyright (C) 2016-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Lem Dulfo, Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
@ -20,8 +20,6 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -38,6 +36,8 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NavUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2018 Andreas Shimokawa
|
||||
/* Copyright (C) 2018-2019 Andreas Shimokawa, Carsten Pfeiffer, Cre3per,
|
||||
Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -24,9 +25,10 @@ import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
@ -35,9 +37,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
|
||||
public class FindPhoneActivity extends AbstractGBActivity {
|
||||
@ -60,6 +61,7 @@ public class FindPhoneActivity extends AbstractGBActivity {
|
||||
}
|
||||
};
|
||||
|
||||
Vibrator mVibrator;
|
||||
AudioManager mAudioManager;
|
||||
int userVolume;
|
||||
MediaPlayer mp;
|
||||
@ -71,7 +73,6 @@ public class FindPhoneActivity extends AbstractGBActivity {
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_FOUND);
|
||||
filter.addAction(DeviceService.ACTION_HEARTRATE_MEASUREMENT);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
registerReceiver(mReceiver, filter); // for ACTION_FOUND
|
||||
|
||||
@ -82,10 +83,26 @@ public class FindPhoneActivity extends AbstractGBActivity {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
vibrate();
|
||||
playRingtone();
|
||||
}
|
||||
|
||||
public void playRingtone(){
|
||||
private void vibrate(){
|
||||
mVibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
|
||||
|
||||
long[] vibrationPattern = new long[]{ 1000, 1000 };
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
VibrationEffect vibrationEffect = VibrationEffect.createWaveform(vibrationPattern, 0);
|
||||
|
||||
mVibrator.vibrate(vibrationEffect);
|
||||
} else {
|
||||
mVibrator.vibrate(vibrationPattern, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void playRingtone(){
|
||||
mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
|
||||
if (mAudioManager != null) {
|
||||
userVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
|
||||
@ -101,12 +118,20 @@ public class FindPhoneActivity extends AbstractGBActivity {
|
||||
mp.prepare();
|
||||
mp.start();
|
||||
} catch (IOException ignore) {
|
||||
LOG.warn("problem playing ringtone");
|
||||
}
|
||||
mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), AudioManager.FLAG_PLAY_SOUND);
|
||||
|
||||
if (mAudioManager != null) {
|
||||
userVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
|
||||
mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), AudioManager.FLAG_PLAY_SOUND);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopSound() {
|
||||
private void stopVibration() {
|
||||
mVibrator.cancel();
|
||||
}
|
||||
|
||||
private void stopSound() {
|
||||
mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, userVolume, AudioManager.FLAG_PLAY_SOUND);
|
||||
mp.stop();
|
||||
mp.reset();
|
||||
@ -115,7 +140,10 @@ public class FindPhoneActivity extends AbstractGBActivity {
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
stopVibration();
|
||||
stopSound();
|
||||
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
unregisterReceiver(mReceiver);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Lem Dulfo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
@ -23,8 +23,6 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
@ -39,6 +37,8 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.ItemWithDetailsAdapter;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2018 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2019 Carsten Pfeiffer, Dikay900
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -0,0 +1,233 @@
|
||||
/* Copyright (C) 2018-2019 abettenburg, AndrewBedscastle, 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.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.greenrobot.dao.query.Query;
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.AppBlacklistAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntry;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntryDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class NotificationFilterActivity extends AbstractGBActivity {
|
||||
|
||||
public static final int NOTIFICATION_FILTER_MODE_NONE = 0;
|
||||
public static final int NOTIFICATION_FILTER_MODE_WHITELIST = 1;
|
||||
public static final int NOTIFICATION_FILTER_MODE_BLACKLIST = 2;
|
||||
public static final int NOTIFICATION_FILTER_SUBMODE_ANY = 0;
|
||||
public static final int NOTIFICATION_FILTER_SUBMODE_ALL = 1;
|
||||
|
||||
private Button mButtonSave;
|
||||
private Spinner mSpinnerFilterMode;
|
||||
private Spinner mSpinnerFilterSubMode;
|
||||
private NotificationFilter mNotificationFilter;
|
||||
private EditText mEditTextWords;
|
||||
private List<String> mWordsList = new ArrayList<>();
|
||||
private List<Long> mFilterEntryIds = new ArrayList<>();
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NotificationFilterActivity.class);
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_notification_filter);
|
||||
|
||||
String packageName = getIntent().getStringExtra(AppBlacklistAdapter.STRING_EXTRA_PACKAGE_NAME);
|
||||
|
||||
if (StringUtils.isBlank(packageName)) {
|
||||
this.finish();
|
||||
}
|
||||
|
||||
packageName = packageName.toLowerCase();
|
||||
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
|
||||
NotificationFilterDao notificationFilterDao = db.getDaoSession().getNotificationFilterDao();
|
||||
NotificationFilterEntryDao notificationFilterEntryDao = db.getDaoSession().getNotificationFilterEntryDao();
|
||||
|
||||
Query<NotificationFilter> query = notificationFilterDao.queryBuilder().where(NotificationFilterDao.Properties.AppIdentifier.eq(packageName)).build();
|
||||
mNotificationFilter = query.unique();
|
||||
|
||||
if (mNotificationFilter == null) {
|
||||
mNotificationFilter = new NotificationFilter();
|
||||
mNotificationFilter.setAppIdentifier(packageName);
|
||||
LOG.debug("New Notification Filter");
|
||||
} else {
|
||||
LOG.debug("Loaded existing notification filter");
|
||||
Query<NotificationFilterEntry> queryEntries = notificationFilterEntryDao.queryBuilder().where(NotificationFilterEntryDao.Properties.NotificationFilterId.eq(mNotificationFilter.getId())).build();
|
||||
List<NotificationFilterEntry> filterEntries = queryEntries.list();
|
||||
for (NotificationFilterEntry temp : filterEntries) {
|
||||
mWordsList.add(temp.getNotificationFilterContent());
|
||||
mFilterEntryIds.add(temp.getId());
|
||||
LOG.debug("Loaded filter word: " + temp.getNotificationFilterContent());
|
||||
}
|
||||
}
|
||||
|
||||
setupView(db);
|
||||
|
||||
} catch (Exception e) {
|
||||
GB.toast(this, "Error accessing the database: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
this.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void setupView(DBHandler db) {
|
||||
|
||||
mSpinnerFilterMode = findViewById(R.id.spinnerFilterMode);
|
||||
mSpinnerFilterMode.setSelection(mNotificationFilter.getNotificationFilterMode());
|
||||
|
||||
mSpinnerFilterSubMode = findViewById(R.id.spinnerSubMode);
|
||||
mSpinnerFilterMode.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long id) {
|
||||
switch (pos) {
|
||||
case NOTIFICATION_FILTER_MODE_NONE:
|
||||
mEditTextWords.setEnabled(false);
|
||||
mSpinnerFilterSubMode.setEnabled(false);
|
||||
break;
|
||||
case NOTIFICATION_FILTER_MODE_BLACKLIST:
|
||||
case NOTIFICATION_FILTER_MODE_WHITELIST:
|
||||
mEditTextWords.setEnabled(true);
|
||||
mSpinnerFilterSubMode.setEnabled(true);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
mSpinnerFilterSubMode.setSelection(mNotificationFilter.getNotificationFilterSubMode());
|
||||
|
||||
mEditTextWords = findViewById(R.id.editTextWords);
|
||||
|
||||
if (!mWordsList.isEmpty()) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String temp : mWordsList) {
|
||||
builder.append(temp);
|
||||
builder.append("\n");
|
||||
}
|
||||
mEditTextWords.setText(builder.toString());
|
||||
}
|
||||
|
||||
mEditTextWords.setEnabled(mSpinnerFilterMode.getSelectedItemPosition() == NOTIFICATION_FILTER_MODE_NONE);
|
||||
|
||||
mButtonSave = findViewById(R.id.buttonSaveFilter);
|
||||
|
||||
mButtonSave.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
saveFilter();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void saveFilter() {
|
||||
// TODO: check for modifications, only save if something changed
|
||||
String words = mEditTextWords.getText().toString();
|
||||
|
||||
if (StringUtils.isBlank(words) && mSpinnerFilterMode.getSelectedItemPosition() != NOTIFICATION_FILTER_MODE_NONE) {
|
||||
Toast.makeText(NotificationFilterActivity.this, R.string.toast_notification_filter_words_empty_hint, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
NotificationFilterDao notificationFilterDao = db.getDaoSession().getNotificationFilterDao();
|
||||
NotificationFilterEntryDao notificationFilterEntryDao = db.getDaoSession().getNotificationFilterEntryDao();
|
||||
|
||||
debugOutput(notificationFilterDao);
|
||||
|
||||
mNotificationFilter.setNotificationFilterMode(mSpinnerFilterMode.getSelectedItemPosition());
|
||||
mNotificationFilter.setNotificationFilterSubMode(mSpinnerFilterSubMode.getSelectedItemPosition());
|
||||
|
||||
notificationFilterEntryDao.deleteByKeyInTx(mFilterEntryIds);
|
||||
|
||||
long filterId = notificationFilterDao.insertOrReplace(mNotificationFilter);
|
||||
|
||||
// only save words if filter mode != none
|
||||
if (mNotificationFilter.getNotificationFilterMode() != NOTIFICATION_FILTER_MODE_NONE) {
|
||||
String[] wordsSplitted = words.split("\n");
|
||||
for (String temp : wordsSplitted) {
|
||||
|
||||
if (StringUtils.isBlank(temp)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
temp = temp.trim();
|
||||
NotificationFilterEntry notificationFilterEntry = new NotificationFilterEntry();
|
||||
notificationFilterEntry.setNotificationFilterContent(temp);
|
||||
notificationFilterEntry.setNotificationFilterId(filterId);
|
||||
notificationFilterEntryDao.insert(notificationFilterEntry);
|
||||
}
|
||||
}
|
||||
|
||||
Toast.makeText(NotificationFilterActivity.this, R.string.toast_notification_filter_saved_successfully, Toast.LENGTH_SHORT).show();
|
||||
NotificationFilterActivity.this.finish();
|
||||
|
||||
} catch (Exception e) {
|
||||
GB.toast(NotificationFilterActivity.this, "Error accessing the database: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only used for debugging purposes
|
||||
*
|
||||
* @param notificationFilterDao {@link NotificationFilterDao}
|
||||
*/
|
||||
private void debugOutput(NotificationFilterDao notificationFilterDao) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
|
||||
List<NotificationFilter> filters = notificationFilterDao.loadAll();
|
||||
|
||||
LOG.info("Saved filters");
|
||||
|
||||
for (NotificationFilter temp : filters) {
|
||||
LOG.info("Filter: " + temp.getId() + " " + temp.getAppIdentifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Felix Konstantin Maurer, José Rebelo, Martin, Normano64,
|
||||
Pavel Elagin
|
||||
/* Copyright (C) 2015-2019 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniel Dakhno, Daniele Gobbetti, Felix Konstantin Maurer, José Rebelo,
|
||||
Martin, Normano64, Pavel Elagin, Sebastian Kranz, vanous
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -36,10 +36,11 @@ import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -53,8 +54,10 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.ConfigActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
@ -62,15 +65,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DATEFORMAT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DISPLAY_ITEMS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ENABLE_TEXT_NOTIFICATIONS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_BAND_SCREEN_UNLOCK;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE_OFF;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE_SCHEDULED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE_START;
|
||||
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;
|
||||
@ -104,6 +98,16 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
pref = findPreference("pref_charts");
|
||||
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent enableIntent = new Intent(SettingsActivity.this, ChartsPreferencesActivity.class);
|
||||
startActivity(enableIntent);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
pref = findPreference("pref_key_miband");
|
||||
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
@ -113,6 +117,24 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
}
|
||||
});
|
||||
|
||||
pref = findPreference("pref_key_qhybrid");
|
||||
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
startActivity(new Intent(SettingsActivity.this, ConfigActivity.class));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
pref = findPreference("pref_key_zetime");
|
||||
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent enableIntent = new Intent(SettingsActivity.this, ZeTimePreferenceActivity.class);
|
||||
startActivity(enableIntent);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
pref = findPreference("pref_key_blacklist");
|
||||
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
@ -284,16 +306,16 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
|
||||
pref = findPreference("weather_city");
|
||||
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
// reset city id and force a new lookup
|
||||
GBApplication.getPrefs().getPreferences().edit().putString("weather_cityid", null).apply();
|
||||
preference.setSummary(newVal.toString());
|
||||
Intent intent = new Intent("GB_UPDATE_WEATHER");
|
||||
intent.setPackage(BuildConfig.APPLICATION_ID);
|
||||
sendBroadcast(intent);
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
// reset city id and force a new lookup
|
||||
GBApplication.getPrefs().getPreferences().edit().putString("weather_cityid", null).apply();
|
||||
preference.setSummary(newVal.toString());
|
||||
Intent intent = new Intent("GB_UPDATE_WEATHER");
|
||||
intent.setPackage(BuildConfig.APPLICATION_ID);
|
||||
sendBroadcast(intent);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
pref = findPreference(GBPrefs.AUTO_EXPORT_LOCATION);
|
||||
@ -356,146 +378,6 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
autoFetchInterval);
|
||||
pref.setSummary(summary);
|
||||
|
||||
|
||||
|
||||
final Preference displayPages = findPreference("bip_display_items");
|
||||
displayPages.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference setDateFormat = findPreference(PREF_MI2_DATEFORMAT);
|
||||
setDateFormat.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DATEFORMAT);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference miBand2DisplayItems = findPreference(PREF_MI2_DISPLAY_ITEMS);
|
||||
miBand2DisplayItems.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference miBand3ScreenUnlock = findPreference(PREF_MI3_BAND_SCREEN_UNLOCK);
|
||||
miBand3ScreenUnlock.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI3_BAND_SCREEN_UNLOCK);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference miBand3DisplayItems = findPreference("miband3_display_items");
|
||||
miBand3DisplayItems.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
String nightModeState = prefs.getString(MiBandConst.PREF_MI3_NIGHT_MODE, PREF_MI3_NIGHT_MODE_OFF);
|
||||
boolean nightModeScheduled = nightModeState.equals(PREF_MI3_NIGHT_MODE_SCHEDULED);
|
||||
|
||||
final Preference nightModeStart = findPreference(PREF_MI3_NIGHT_MODE_START);
|
||||
nightModeStart.setEnabled(nightModeScheduled);
|
||||
nightModeStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI3_NIGHT_MODE_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
final Preference nightModeEnd = findPreference(PREF_MI3_NIGHT_MODE_END);
|
||||
nightModeEnd.setEnabled(nightModeScheduled);
|
||||
nightModeEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI3_NIGHT_MODE_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
final Preference nightMode = findPreference(PREF_MI3_NIGHT_MODE);
|
||||
nightMode.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean scheduled = PREF_MI3_NIGHT_MODE_SCHEDULED.equals(newVal.toString());
|
||||
|
||||
nightModeStart.setEnabled(scheduled);
|
||||
nightModeEnd.setEnabled(scheduled);
|
||||
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI3_NIGHT_MODE);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference corDisplayItems = findPreference("cor_display_items");
|
||||
corDisplayItems.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Get all receivers of Media Buttons
|
||||
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
||||
|
||||
@ -563,8 +445,7 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
|
||||
}
|
||||
}
|
||||
catch (Exception fdfsdfds) {
|
||||
} catch (Exception fdfsdfds) {
|
||||
LOG.warn("fuck");
|
||||
}
|
||||
}
|
||||
@ -624,7 +505,6 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
PREF_USER_WEIGHT_KG,
|
||||
PREF_USER_SLEEP_DURATION,
|
||||
PREF_USER_STEPS_GOAL,
|
||||
PREF_MI2_ENABLE_TEXT_NOTIFICATIONS,
|
||||
"weather_city",
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -0,0 +1,145 @@
|
||||
/* Copyright (C) 2019 vanous
|
||||
|
||||
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.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class WidgetAlarmsActivity extends Activity implements View.OnClickListener {
|
||||
|
||||
TextView textView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Context appContext = this.getApplicationContext();
|
||||
if (appContext instanceof GBApplication) {
|
||||
GBApplication gbApp = (GBApplication) appContext;
|
||||
GBDevice selectedDevice = gbApp.getDeviceManager().getSelectedDevice();
|
||||
if (selectedDevice == null || !selectedDevice.isInitialized()) {
|
||||
GB.toast(this,
|
||||
this.getString(R.string.not_connected),
|
||||
Toast.LENGTH_LONG, GB.WARN);
|
||||
|
||||
} else {
|
||||
setContentView(R.layout.widget_alarms_activity_list);
|
||||
int userSleepDuration = new ActivityUser().getSleepDuration();
|
||||
textView = findViewById(R.id.alarm5);
|
||||
if (userSleepDuration > 0) {
|
||||
Resources res = getResources();
|
||||
textView.setText(String.format(res.getQuantityString(R.plurals.widget_alarm_target_hours, userSleepDuration, userSleepDuration)));
|
||||
} else {
|
||||
textView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
switch (v.getId()) {
|
||||
case R.id.alarm1:
|
||||
setAlarm(5);
|
||||
break;
|
||||
case R.id.alarm2:
|
||||
setAlarm(10);
|
||||
break;
|
||||
case R.id.alarm3:
|
||||
setAlarm(20);
|
||||
break;
|
||||
case R.id.alarm4:
|
||||
setAlarm(60);
|
||||
break;
|
||||
case R.id.alarm5:
|
||||
setAlarm(0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
//this is to prevent screen flashing during closing
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finish();
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
|
||||
public void setAlarm(int duration) {
|
||||
// current timestamp
|
||||
GregorianCalendar calendar = new GregorianCalendar();
|
||||
if (duration > 0) {
|
||||
calendar.add(Calendar.MINUTE, duration);
|
||||
} else {
|
||||
int userSleepDuration = new ActivityUser().getSleepDuration();
|
||||
// add preferred sleep duration
|
||||
if (userSleepDuration > 0) {
|
||||
calendar.add(Calendar.HOUR_OF_DAY, userSleepDuration);
|
||||
} else { // probably testing
|
||||
calendar.add(Calendar.MINUTE, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// overwrite the first alarm and activate it, without
|
||||
|
||||
Context appContext = this.getApplicationContext();
|
||||
if (appContext instanceof GBApplication) {
|
||||
GBApplication gbApp = (GBApplication) appContext;
|
||||
GBDevice selectedDevice = gbApp.getDeviceManager().getSelectedDevice();
|
||||
if (selectedDevice == null || !selectedDevice.isInitialized()) {
|
||||
GB.toast(this,
|
||||
this.getString(R.string.appwidget_not_connected),
|
||||
Toast.LENGTH_LONG, GB.WARN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int hours = calendar.get(Calendar.HOUR_OF_DAY);
|
||||
int minutes = calendar.get(Calendar.MINUTE);
|
||||
|
||||
GB.toast(this,
|
||||
this.getString(R.string.appwidget_setting_alarm, hours, minutes),
|
||||
Toast.LENGTH_SHORT, GB.INFO);
|
||||
|
||||
Alarm alarm = AlarmUtils.createSingleShot(0, true, calendar);
|
||||
ArrayList<Alarm> alarms = new ArrayList<>(1);
|
||||
alarms.add(alarm);
|
||||
GBApplication.deviceService().onSetAlarms(alarms);
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Konrad Iturbe, Lem Dulfo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
@ -24,12 +24,6 @@ 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;
|
||||
@ -37,6 +31,8 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.PopupMenu;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -47,6 +43,11 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ExternalPebbleJSActivity;
|
||||
@ -428,7 +429,7 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
||||
startActivity(startIntent);
|
||||
return true;
|
||||
case R.id.appmanager_app_openinstore:
|
||||
String url = "https://pebble-appstore.romanport.com/" + ((selectedApp.getType() == GBDeviceApp.Type.WATCHFACE) ? "watchfaces" : "watchapps") + "/0/?query=" + selectedApp.getName();
|
||||
String url = "https://apps.rebble.io/en_US/search/" + ((selectedApp.getType() == GBDeviceApp.Type.WATCHFACE) ? "watchfaces" : "watchapps") + "/1/?native=true&?query=" + Uri.encode(selectedApp.getName());
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(url));
|
||||
startActivity(intent);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
/* Copyright (C) 2016-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
@ -20,14 +20,11 @@ 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 com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -40,6 +37,10 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractFragmentPagerAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragmentActivity;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2018 Andreas Shimokawa
|
||||
/* Copyright (C) 2016-2019 Andreas Shimokawa
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2018 Andreas Shimokawa, Daniele Gobbetti
|
||||
/* Copyright (C) 2016-2019 Andreas Shimokawa, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2018 Andreas Shimokawa
|
||||
/* Copyright (C) 2016-2019 Andreas Shimokawa
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, walkjivefly
|
||||
/* Copyright (C) 2015-2019 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Dikay900, Pavel Elagin, vanous, walkjivefly
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -23,23 +23,23 @@ 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 androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
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.ChartData;
|
||||
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.formatter.ValueFormatter;
|
||||
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -69,6 +69,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
/**
|
||||
* A base class fragment to be used with ChartsActivity. The fragment can supply
|
||||
@ -151,8 +152,12 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
if (intentFilterActions != null) {
|
||||
mIntentFilterActions.addAll(Arrays.asList(intentFilterActions));
|
||||
}
|
||||
mIntentFilterActions.add(ChartsHost.DATE_NEXT);
|
||||
mIntentFilterActions.add(ChartsHost.DATE_PREV);
|
||||
mIntentFilterActions.add(ChartsHost.DATE_NEXT_DAY);
|
||||
mIntentFilterActions.add(ChartsHost.DATE_PREV_DAY);
|
||||
mIntentFilterActions.add(ChartsHost.DATE_NEXT_WEEK);
|
||||
mIntentFilterActions.add(ChartsHost.DATE_PREV_WEEK);
|
||||
mIntentFilterActions.add(ChartsHost.DATE_NEXT_MONTH);
|
||||
mIntentFilterActions.add(ChartsHost.DATE_PREV_MONTH);
|
||||
mIntentFilterActions.add(ChartsHost.REFRESH);
|
||||
}
|
||||
|
||||
@ -170,12 +175,18 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
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);
|
||||
if (prefs.getBoolean("chart_heartrate_color", false)) {
|
||||
HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_alternative);
|
||||
}else{
|
||||
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);
|
||||
@ -243,10 +254,18 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
String action = intent.getAction();
|
||||
if (ChartsHost.REFRESH.equals(action)) {
|
||||
refresh();
|
||||
} else if (ChartsHost.DATE_NEXT.equals(action)) {
|
||||
handleDateNext(getStartDate(), getEndDate());
|
||||
} else if (ChartsHost.DATE_PREV.equals(action)) {
|
||||
handleDatePrev(getStartDate(), getEndDate());
|
||||
} else if (ChartsHost.DATE_NEXT_DAY.equals(action)) {
|
||||
handleDate(getStartDate(), getEndDate(),+1);
|
||||
} else if (ChartsHost.DATE_PREV_DAY.equals(action)) {
|
||||
handleDate(getStartDate(), getEndDate(),-1);
|
||||
} else if (ChartsHost.DATE_NEXT_WEEK.equals(action)) {
|
||||
handleDate(getStartDate(), getEndDate(),+7);
|
||||
} else if (ChartsHost.DATE_PREV_WEEK.equals(action)) {
|
||||
handleDate(getStartDate(), getEndDate(),-7);
|
||||
} else if (ChartsHost.DATE_NEXT_MONTH.equals(action)) {
|
||||
handleDate(getStartDate(), getEndDate(),+30);
|
||||
} else if (ChartsHost.DATE_PREV_MONTH.equals(action)) {
|
||||
handleDate(getStartDate(), getEndDate(),-30);
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,31 +275,17 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
*
|
||||
* @param startDate
|
||||
* @param endDate
|
||||
* @param Offset
|
||||
*/
|
||||
protected void handleDatePrev(Date startDate, Date endDate) {
|
||||
protected void handleDate(Date startDate, Date endDate, Integer Offset) {
|
||||
if (isVisibleInActivity()) {
|
||||
if (!shiftDates(startDate, endDate, -1)) {
|
||||
if (!shiftDates(startDate, endDate, Offset)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
refreshIfVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation shifts the dates by one day, if visible
|
||||
* and calls #refreshIfVisible().
|
||||
*
|
||||
* @param startDate
|
||||
* @param endDate
|
||||
*/
|
||||
protected void handleDateNext(Date startDate, Date endDate) {
|
||||
if (isVisibleInActivity()) {
|
||||
if (!shiftDates(startDate, endDate, +1)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
refreshIfVisible();
|
||||
}
|
||||
|
||||
protected void refreshIfVisible() {
|
||||
if (isVisibleInActivity()) {
|
||||
@ -572,7 +577,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
lineData = new LineData();
|
||||
}
|
||||
|
||||
IAxisValueFormatter xValueFormatter = new SampleXLabelFormatter(tsTranslation);
|
||||
ValueFormatter xValueFormatter = new SampleXLabelFormatter(tsTranslation);
|
||||
return new DefaultChartsData(lineData, xValueFormatter);
|
||||
}
|
||||
|
||||
@ -712,6 +717,29 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
return samples;
|
||||
}
|
||||
|
||||
protected List<? extends ActivitySample> getSamplesofSleep(DBHandler db, GBDevice device) {
|
||||
int SLEEP_HOUR_LIMIT = 12;
|
||||
|
||||
int tsStart = getTSStart();
|
||||
Calendar day = GregorianCalendar.getInstance();
|
||||
day.setTimeInMillis(tsStart * 1000L);
|
||||
day.set(Calendar.HOUR_OF_DAY, SLEEP_HOUR_LIMIT);
|
||||
day.set(Calendar.MINUTE, 0);
|
||||
day.set(Calendar.SECOND, 0);
|
||||
tsStart = toTimestamp(day.getTime());
|
||||
|
||||
int tsEnd = getTSEnd();
|
||||
day.setTimeInMillis(tsEnd* 1000L);
|
||||
day.set(Calendar.HOUR_OF_DAY, SLEEP_HOUR_LIMIT);
|
||||
day.set(Calendar.MINUTE, 0);
|
||||
day.set(Calendar.SECOND, 0);
|
||||
tsEnd = toTimestamp(day.getTime());
|
||||
|
||||
List<ActivitySample> samples = (List<ActivitySample>) getSamples(db, device, tsStart, tsEnd);
|
||||
ensureStartAndEndSamples(samples, tsStart, tsEnd);
|
||||
return samples;
|
||||
}
|
||||
|
||||
protected void ensureStartAndEndSamples(List<ActivitySample> samples, int tsStart, int tsEnd) {
|
||||
if (samples == null || samples.isEmpty()) {
|
||||
return;
|
||||
@ -753,14 +781,14 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
|
||||
public static class DefaultChartsData<T extends ChartData<?>> extends ChartsData {
|
||||
private final T data;
|
||||
private IAxisValueFormatter xValueFormatter;
|
||||
private ValueFormatter xValueFormatter;
|
||||
|
||||
public DefaultChartsData(T data, IAxisValueFormatter xValueFormatter) {
|
||||
public DefaultChartsData(T data, ValueFormatter xValueFormatter) {
|
||||
this.xValueFormatter = xValueFormatter;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public IAxisValueFormatter getXValueFormatter() {
|
||||
public ValueFormatter getXValueFormatter() {
|
||||
return xValueFormatter;
|
||||
}
|
||||
|
||||
@ -769,7 +797,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
}
|
||||
}
|
||||
|
||||
protected static class SampleXLabelFormatter implements IAxisValueFormatter {
|
||||
protected static class SampleXLabelFormatter extends ValueFormatter {
|
||||
private final TimestampTranslation tsTranslation;
|
||||
SimpleDateFormat annotationDateFormat = new SimpleDateFormat("HH:mm");
|
||||
// SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
|
||||
@ -781,7 +809,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
}
|
||||
// TODO: this does not work. Cannot use precomputed labels
|
||||
@Override
|
||||
public String getFormattedValue(float value, AxisBase axis) {
|
||||
public String getFormattedValue(float value) {
|
||||
cal.clear();
|
||||
int ts = (int) value;
|
||||
cal.setTimeInMillis(tsTranslation.toOriginalValue(ts) * 1000L);
|
||||
@ -791,7 +819,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
}
|
||||
}
|
||||
|
||||
protected static class PreformattedXIndexLabelFormatter implements IAxisValueFormatter {
|
||||
protected static class PreformattedXIndexLabelFormatter extends ValueFormatter {
|
||||
private ArrayList<String> xLabels;
|
||||
|
||||
public PreformattedXIndexLabelFormatter(ArrayList<String> xLabels) {
|
||||
@ -799,7 +827,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
|
||||
}
|
||||
@Override
|
||||
public String getFormattedValue(float value, AxisBase axis) {
|
||||
public String getFormattedValue(float value) {
|
||||
int index = (int) value;
|
||||
if (xLabels == null || index >= xLabels.size()) {
|
||||
return String.valueOf(value);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 0nse, Alberto, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti
|
||||
/* Copyright (C) 2015-2019 0nse, Alberto, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Pavel Elagin, vanous
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -37,8 +37,7 @@ import com.github.mikephil.charting.data.ChartData;
|
||||
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 com.github.mikephil.charting.formatter.ValueFormatter;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -48,6 +47,7 @@ import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
@ -58,7 +58,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
|
||||
|
||||
public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class);
|
||||
protected final int TOTAL_DAYS = 7;
|
||||
protected final int TOTAL_DAYS = getRangeDays();
|
||||
|
||||
private Locale mLocale;
|
||||
private int mTargetValue = 0;
|
||||
@ -87,6 +87,10 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
setupLegend(mWeekChart);
|
||||
mTodayPieChart.setCenterText(mcd.getDayData().centerText);
|
||||
mTodayPieChart.setData(mcd.getDayData().data);
|
||||
//set custom renderer for 30days bar charts
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
mWeekChart.setRenderer(new AngledLabelsChartRenderer(mWeekChart, mWeekChart.getAnimator(), mWeekChart.getViewPortHandler()));
|
||||
}
|
||||
|
||||
mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
|
||||
mWeekChart.setData(mcd.getWeekBeforeData().getData());
|
||||
@ -102,6 +106,17 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
// mBalanceView.setText(getBalanceMessage(balance));
|
||||
}
|
||||
|
||||
private String getWeeksChartsLabel(Calendar day){
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
//month, show day date
|
||||
return String.valueOf(day.get(Calendar.DAY_OF_MONTH));
|
||||
}
|
||||
else{
|
||||
//week, show short day name
|
||||
return day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale);
|
||||
}
|
||||
}
|
||||
|
||||
private WeekChartsData<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, -TOTAL_DAYS);
|
||||
@ -114,7 +129,7 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
|
||||
balance += calculateBalance(amounts);
|
||||
entries.add(new BarEntry(counter, getTotalsForActivityAmounts(amounts)));
|
||||
labels.add(day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale));
|
||||
labels.add(getWeeksChartsLabel(day));
|
||||
day.add(Calendar.DATE, 1);
|
||||
}
|
||||
|
||||
@ -130,7 +145,28 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
barChart.getAxisLeft().removeAllLimitLines();
|
||||
barChart.getAxisLeft().addLimitLine(target);
|
||||
|
||||
return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue));
|
||||
float average = 0;
|
||||
if (TOTAL_DAYS > 0) {
|
||||
average = Math.abs(balance / TOTAL_DAYS);
|
||||
}
|
||||
LimitLine average_line = new LimitLine(average);
|
||||
average_line.setLabel(getString(R.string.average, getAverage(average)));
|
||||
|
||||
if (average > (mTargetValue)) {
|
||||
average_line.setLineColor(Color.GREEN);
|
||||
average_line.setTextColor(Color.GREEN);
|
||||
}
|
||||
else {
|
||||
average_line.setLineColor(Color.RED);
|
||||
average_line.setTextColor(Color.RED);
|
||||
}
|
||||
if (average > 0) {
|
||||
if (GBApplication.getPrefs().getBoolean("charts_show_average", true)) {
|
||||
barChart.getAxisLeft().addLimitLine(average_line);
|
||||
}
|
||||
}
|
||||
|
||||
return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue));
|
||||
}
|
||||
|
||||
private DayData refreshDayPie(DBHandler db, Calendar day, GBDevice device) {
|
||||
@ -315,6 +351,16 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
return amounts;
|
||||
}
|
||||
|
||||
private int getRangeDays(){
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
return 30;}
|
||||
else{
|
||||
return 7;
|
||||
}
|
||||
}
|
||||
|
||||
abstract String getAverage(float value);
|
||||
|
||||
abstract int getGoal();
|
||||
|
||||
abstract int getOffsetHours();
|
||||
@ -325,11 +371,11 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
|
||||
abstract String[] getPieLabels();
|
||||
|
||||
abstract IValueFormatter getPieValueFormatter();
|
||||
abstract ValueFormatter getPieValueFormatter();
|
||||
|
||||
abstract IValueFormatter getBarValueFormatter();
|
||||
abstract ValueFormatter getBarValueFormatter();
|
||||
|
||||
abstract IAxisValueFormatter getYAxisFormatter();
|
||||
abstract ValueFormatter getYAxisFormatter();
|
||||
|
||||
abstract int[] getColors();
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Pavel Elagin, Vebryn
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Pavel Elagin, vanous, Vebryn
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -17,26 +17,26 @@
|
||||
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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
class ActivityAnalysis {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ActivityAnalysis.class);
|
||||
public class ActivityAnalysis {
|
||||
public 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) {
|
||||
public 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);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Pavel Elagin
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Dikay900, Pavel Elagin
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -143,7 +143,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
|
||||
|
||||
@Override
|
||||
protected void renderCharts() {
|
||||
mChart.animateX(ANIM_TIME, Easing.EasingOption.EaseInOutQuart);
|
||||
mChart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
|
||||
// mChart.invalidate();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,46 @@
|
||||
/* Copyright (C) 2019 Andreas Shimokawa, vanous
|
||||
|
||||
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.Canvas;
|
||||
|
||||
import com.github.mikephil.charting.animation.ChartAnimator;
|
||||
import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider;
|
||||
import com.github.mikephil.charting.renderer.BarChartRenderer;
|
||||
import com.github.mikephil.charting.utils.ViewPortHandler;
|
||||
|
||||
public class AngledLabelsChartRenderer extends BarChartRenderer {
|
||||
AngledLabelsChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
|
||||
super(chart, animator, viewPortHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawValue(Canvas canvas, String valueText, float x, float y, int color) {
|
||||
|
||||
mValuePaint.setColor(color);
|
||||
|
||||
//move position to the center of bar
|
||||
x=x+8;
|
||||
y=y-25;
|
||||
|
||||
canvas.save();
|
||||
canvas.rotate(-90, x, y);
|
||||
|
||||
canvas.drawText(valueText, x, y, mValuePaint);
|
||||
|
||||
canvas.restore();
|
||||
}}
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, Vebryn
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, vanous, Vebryn
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -23,12 +23,6 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
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.util.AttributeSet;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@ -43,6 +37,12 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractFragmentPagerAdapter;
|
||||
@ -175,20 +175,52 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
}
|
||||
});
|
||||
|
||||
Button mPrevButton = findViewById(R.id.charts_previous);
|
||||
Button mPrevButton = findViewById(R.id.charts_previous_day);
|
||||
mPrevButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handlePrevButtonClicked();
|
||||
handleButtonClicked(DATE_PREV_DAY);
|
||||
}
|
||||
});
|
||||
Button mNextButton = findViewById(R.id.charts_next);
|
||||
Button mNextButton = findViewById(R.id.charts_next_day);
|
||||
mNextButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handleNextButtonClicked();
|
||||
handleButtonClicked(DATE_NEXT_DAY);
|
||||
}
|
||||
});
|
||||
|
||||
Button mPrevWeekButton = findViewById(R.id.charts_previous_week);
|
||||
mPrevWeekButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handleButtonClicked(DATE_PREV_WEEK);
|
||||
}
|
||||
});
|
||||
Button mNextWeekButton = findViewById(R.id.charts_next_week);
|
||||
mNextWeekButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handleButtonClicked(DATE_NEXT_WEEK);
|
||||
}
|
||||
});
|
||||
|
||||
Button mPrevMonthButton = findViewById(R.id.charts_previous_month);
|
||||
mPrevMonthButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handleButtonClicked(DATE_PREV_MONTH);
|
||||
}
|
||||
});
|
||||
Button mNextMonthButton = findViewById(R.id.charts_next_month);
|
||||
mNextMonthButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handleButtonClicked(DATE_NEXT_MONTH);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
private String formatDetailedDuration() {
|
||||
@ -229,12 +261,8 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
return mEndDate;
|
||||
}
|
||||
|
||||
private void handleNextButtonClicked() {
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(DATE_NEXT));
|
||||
}
|
||||
|
||||
private void handlePrevButtonClicked() {
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(DATE_PREV));
|
||||
private void handleButtonClicked(String Action) {
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(Action));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -255,12 +283,24 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == 1) {
|
||||
this.recreate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.charts_fetch_activity_data:
|
||||
fetchActivityData();
|
||||
return true;
|
||||
case R.id.prefs_charts_menu:
|
||||
Intent settingsIntent = new Intent(this, ChartsPreferencesActivity.class);
|
||||
startActivityForResult(settingsIntent,1);
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -338,6 +378,24 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
return 5;
|
||||
}
|
||||
|
||||
private String getSleepTitle() {
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
return getString(R.string.weeksleepchart_sleep_a_month);
|
||||
}
|
||||
else{
|
||||
return getString(R.string.weeksleepchart_sleep_a_week);
|
||||
}
|
||||
}
|
||||
|
||||
public String getStepsTitle() {
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
return getString(R.string.weekstepschart_steps_a_month);
|
||||
}
|
||||
else{
|
||||
return getString(R.string.weekstepschart_steps_a_week);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
switch (position) {
|
||||
@ -346,9 +404,9 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
case 1:
|
||||
return getString(R.string.sleepchart_your_sleep);
|
||||
case 2:
|
||||
return getString(R.string.weeksleepchart_sleep_a_week);
|
||||
return getSleepTitle();
|
||||
case 3:
|
||||
return getString(R.string.weekstepschart_steps_a_week);
|
||||
return getStepsTitle();
|
||||
case 4:
|
||||
return getString(R.string.stats_title);
|
||||
case 5:
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2018 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2019 Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, vanous
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -23,8 +23,14 @@ import java.util.Date;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public interface ChartsHost {
|
||||
String DATE_PREV = ChartsActivity.class.getName().concat(".date_prev");
|
||||
String DATE_NEXT = ChartsActivity.class.getName().concat(".date_next");
|
||||
String DATE_PREV_DAY = ChartsActivity.class.getName().concat(".date_prev_day");
|
||||
String DATE_NEXT_DAY = ChartsActivity.class.getName().concat(".date_next_day");
|
||||
String DATE_PREV_WEEK = ChartsActivity.class.getName().concat(".date_prev_week");
|
||||
String DATE_NEXT_WEEK = ChartsActivity.class.getName().concat(".date_next_week");
|
||||
String DATE_PREV_MONTH = ChartsActivity.class.getName().concat(".date_prev_month");
|
||||
String DATE_NEXT_MONTH = ChartsActivity.class.getName().concat(".date_next_month");
|
||||
|
||||
|
||||
String REFRESH = ChartsActivity.class.getName().concat(".refresh");
|
||||
|
||||
GBDevice getDevice();
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo,
|
||||
vanous
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -14,12 +15,17 @@
|
||||
|
||||
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;
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||
|
||||
public class GBDeviceEventSleepMonitorResult extends GBDeviceEvent {
|
||||
// FIXME: this is just the low-level data from Morpheuz, we need something generic
|
||||
public int smartalarm_from = -1; // time in minutes relative from 0:00 for smart alarm (earliest)
|
||||
public int smartalarm_to = -1;// time in minutes relative from 0:00 for smart alarm (latest)
|
||||
public int recording_base_timestamp = -1; // timestamp for the first "point", all folowing are +10 minutes offset each
|
||||
public int alarm_gone_off = -1; // time in minutes relative from 0:00 when alarm gone off
|
||||
import android.os.Bundle;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivity;
|
||||
|
||||
public class ChartsPreferencesActivity extends AbstractSettingsActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.charts_preferences);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Cre3per,
|
||||
Daniele Gobbetti, Dikay900, Pavel, Pavel Elagin
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -22,9 +23,6 @@ import android.content.Intent;
|
||||
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;
|
||||
import android.view.ViewGroup;
|
||||
@ -55,6 +53,9 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
||||
@ -63,7 +64,6 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Measurement;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class LiveActivityFragment extends AbstractChartFragment {
|
||||
@ -85,7 +85,6 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
private final Steps mSteps = new Steps();
|
||||
private ScheduledExecutorService pulseScheduler;
|
||||
private int maxStepsResetCounter;
|
||||
private List<Measurement> heartRateValues;
|
||||
private LineDataSet mHeartRateSet;
|
||||
private int mHeartRate;
|
||||
private int mMaxHeartRate = 0;
|
||||
@ -266,14 +265,13 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
|
||||
heartRateValues = new ArrayList<>();
|
||||
tsTranslation = new TimestampTranslation();
|
||||
|
||||
View rootView = inflater.inflate(R.layout.fragment_live_activity, container, false);
|
||||
|
||||
mStepsPerMinuteCurrentChart = (CustomBarChart) rootView.findViewById(R.id.livechart_steps_per_minute_current);
|
||||
mTotalStepsChart = (CustomBarChart) rootView.findViewById(R.id.livechart_steps_total);
|
||||
mStepsPerMinuteHistoryChart = (BarLineChartBase) rootView.findViewById(R.id.livechart_steps_per_minute_history);
|
||||
mStepsPerMinuteCurrentChart = rootView.findViewById(R.id.livechart_steps_per_minute_current);
|
||||
mTotalStepsChart = rootView.findViewById(R.id.livechart_steps_total);
|
||||
mStepsPerMinuteHistoryChart = rootView.findViewById(R.id.livechart_steps_per_minute_history);
|
||||
|
||||
totalStepsEntry = new BarEntry(1, 0);
|
||||
stepsPerMinuteEntry = new BarEntry(1, 0);
|
||||
@ -348,7 +346,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
|
||||
renderCharts();
|
||||
|
||||
// have to enable it again and again to keep it measureing
|
||||
// have to enable it again and again to keep it measuring
|
||||
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(true);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2019 Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -0,0 +1,118 @@
|
||||
/* Copyright (C) 2019 Q-er
|
||||
|
||||
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.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
public class SleepAnalysis {
|
||||
|
||||
public static final long MIN_SESSION_LENGTH = 5 * 60;
|
||||
public static final long MAX_WAKE_PHASE_LENGTH = 2 * 60 * 60;
|
||||
|
||||
public List<SleepSession> calculateSleepSessions(List<? extends ActivitySample> samples) {
|
||||
List<SleepSession> result = new ArrayList<>();
|
||||
|
||||
ActivitySample previousSample = null;
|
||||
Date sleepStart = null;
|
||||
Date sleepEnd = null;
|
||||
long lightSleepDuration = 0;
|
||||
long deepSleepDuration = 0;
|
||||
long durationSinceLastSleep = 0;
|
||||
|
||||
for (ActivitySample sample : samples) {
|
||||
if (isSleep(sample)) {
|
||||
if (sleepStart == null)
|
||||
sleepStart = getDateFromSample(sample);
|
||||
sleepEnd = getDateFromSample(sample);
|
||||
|
||||
durationSinceLastSleep = 0;
|
||||
}
|
||||
|
||||
if (previousSample != null) {
|
||||
long durationSinceLastSample = sample.getTimestamp() - previousSample.getTimestamp();
|
||||
if (sample.getKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
|
||||
lightSleepDuration += durationSinceLastSample;
|
||||
} else if (sample.getKind() == ActivityKind.TYPE_DEEP_SLEEP) {
|
||||
deepSleepDuration += durationSinceLastSample;
|
||||
} else {
|
||||
durationSinceLastSleep += durationSinceLastSample;
|
||||
if (sleepStart != null && durationSinceLastSleep > MAX_WAKE_PHASE_LENGTH) {
|
||||
if (lightSleepDuration + deepSleepDuration > MIN_SESSION_LENGTH)
|
||||
result.add(new SleepSession(sleepStart, sleepEnd, lightSleepDuration, deepSleepDuration));
|
||||
sleepStart = null;
|
||||
sleepEnd = null;
|
||||
lightSleepDuration = 0;
|
||||
deepSleepDuration = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousSample = sample;
|
||||
}
|
||||
if (lightSleepDuration + deepSleepDuration > MIN_SESSION_LENGTH) {
|
||||
result.add(new SleepSession(sleepStart, sleepEnd, lightSleepDuration, deepSleepDuration));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isSleep(ActivitySample sample) {
|
||||
return sample.getKind() == ActivityKind.TYPE_DEEP_SLEEP || sample.getKind() == ActivityKind.TYPE_LIGHT_SLEEP;
|
||||
}
|
||||
|
||||
private Date getDateFromSample(ActivitySample sample) {
|
||||
return new Date(sample.getTimestamp() * 1000L);
|
||||
}
|
||||
|
||||
|
||||
public static class SleepSession {
|
||||
private final Date sleepStart;
|
||||
private final Date sleepEnd;
|
||||
private final long lightSleepDuration;
|
||||
private final long deepSleepDuration;
|
||||
|
||||
private SleepSession(Date sleepStart,
|
||||
Date sleepEnd,
|
||||
long lightSleepDuration,
|
||||
long deepSleepDuration) {
|
||||
this.sleepStart = sleepStart;
|
||||
this.sleepEnd = sleepEnd;
|
||||
this.lightSleepDuration = lightSleepDuration;
|
||||
this.deepSleepDuration = deepSleepDuration;
|
||||
}
|
||||
|
||||
public Date getSleepStart() {
|
||||
return sleepStart;
|
||||
}
|
||||
|
||||
public Date getSleepEnd() {
|
||||
return sleepEnd;
|
||||
}
|
||||
|
||||
public long getLightSleepDuration() {
|
||||
return lightSleepDuration;
|
||||
}
|
||||
|
||||
public long getDeepSleepDuration() {
|
||||
return deepSleepDuration;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Pavel Elagin
|
||||
/* Copyright (C) 2015-2019 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Dikay900, Pavel Elagin, Q-er, vanous
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -20,7 +20,6 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -33,31 +32,32 @@ 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.Entry;
|
||||
import com.github.mikephil.charting.data.LineData;
|
||||
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.IValueFormatter;
|
||||
import com.github.mikephil.charting.utils.ViewPortHandler;
|
||||
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
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.activities.charts.SleepAnalysis.SleepSession;
|
||||
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;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
|
||||
public class SleepChartFragment extends AbstractChartFragment {
|
||||
@ -74,53 +74,69 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
|
||||
@Override
|
||||
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
|
||||
List<? extends ActivitySample> samples = getSamples(db, device);
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
List<? extends ActivitySample> samples;
|
||||
if (prefs.getBoolean("chart_sleep_range_24h", false)) {
|
||||
samples = getSamples(db, device);
|
||||
}else{
|
||||
samples = getSamplesofSleep(db, device);
|
||||
}
|
||||
|
||||
MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples);
|
||||
|
||||
if (!prefs.getBoolean("chart_sleep_range_24h", false)) {
|
||||
if (mySleepChartsData.sleepSessions.size() > 0) {
|
||||
long tstart = mySleepChartsData.sleepSessions.get(0).getSleepStart().getTime() / 1000;
|
||||
long tend = mySleepChartsData.sleepSessions.get(mySleepChartsData.sleepSessions.size() - 1).getSleepEnd().getTime() / 1000;
|
||||
|
||||
for (Iterator<ActivitySample> iterator = (Iterator<ActivitySample>) samples.iterator(); iterator.hasNext(); ) {
|
||||
ActivitySample sample = iterator.next();
|
||||
if (sample.getTimestamp() < tstart || sample.getTimestamp() > tend) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DefaultChartsData chartsData = refresh(device, samples);
|
||||
|
||||
return new MyChartsData(mySleepChartsData, chartsData);
|
||||
}
|
||||
|
||||
private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<? extends ActivitySample> samples) {
|
||||
ActivityAnalysis analysis = new ActivityAnalysis();
|
||||
ActivityAmounts amounts = analysis.calculateActivityAmounts(samples);
|
||||
SleepAnalysis sleepAnalysis = new SleepAnalysis();
|
||||
List<SleepSession> sleepSessions = sleepAnalysis.calculateSleepSessions(samples);
|
||||
|
||||
PieData data = new PieData();
|
||||
List<PieEntry> entries = new ArrayList<>();
|
||||
List<Integer> colors = new ArrayList<>();
|
||||
// int index = 0;
|
||||
long totalSeconds = 0;
|
||||
|
||||
Date startSleep = null;
|
||||
Date endSleep = null;
|
||||
|
||||
for (ActivityAmount amount : amounts.getAmounts()) {
|
||||
if ((amount.getActivityKind() & ActivityKind.TYPE_SLEEP) != 0) {
|
||||
long value = amount.getTotalSeconds();
|
||||
if(startSleep == null){
|
||||
startSleep = amount.getStartDate();
|
||||
} else {
|
||||
if(startSleep.after(amount.getStartDate()))
|
||||
startSleep = amount.getStartDate();
|
||||
}
|
||||
if(endSleep == null){
|
||||
endSleep = amount.getEndDate();
|
||||
} else {
|
||||
if(endSleep.before(amount.getEndDate()))
|
||||
endSleep = amount.getEndDate();
|
||||
}
|
||||
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()));
|
||||
}
|
||||
final long lightSleepDuration = calculateLightSleepDuration(sleepSessions);
|
||||
final long deepSleepDuration = calculateDeepSleepDuration(sleepSessions);
|
||||
|
||||
final long totalSeconds = lightSleepDuration + deepSleepDuration;
|
||||
|
||||
final List<PieEntry> entries;
|
||||
final List<Integer> colors;
|
||||
|
||||
if (sleepSessions.isEmpty()) {
|
||||
entries = Collections.emptyList();
|
||||
colors = Collections.emptyList();
|
||||
} else {
|
||||
entries = Arrays.asList(
|
||||
new PieEntry(lightSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_light_sleep)),
|
||||
new PieEntry(deepSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_deep_sleep))
|
||||
);
|
||||
colors = Arrays.asList(
|
||||
getColorFor(ActivityKind.TYPE_LIGHT_SLEEP),
|
||||
getColorFor(ActivityKind.TYPE_DEEP_SLEEP)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
String totalSleep = DateTimeUtils.formatDurationHoursMinutes(totalSeconds, TimeUnit.SECONDS);
|
||||
PieDataSet set = new PieDataSet(entries, "");
|
||||
set.setValueFormatter(new IValueFormatter() {
|
||||
set.setValueFormatter(new ValueFormatter() {
|
||||
@Override
|
||||
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
|
||||
public String getFormattedValue(float value) {
|
||||
return DateTimeUtils.formatDurationHoursMinutes((long) value, TimeUnit.SECONDS);
|
||||
}
|
||||
});
|
||||
@ -132,27 +148,54 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
data.setDataSet(set);
|
||||
|
||||
//setupLegend(pieChart);
|
||||
return new MySleepChartsData(totalSleep, data, startSleep, endSleep);
|
||||
return new MySleepChartsData(totalSleep, data, sleepSessions);
|
||||
}
|
||||
|
||||
private long calculateLightSleepDuration(List<SleepSession> sleepSessions) {
|
||||
long result = 0;
|
||||
for (SleepSession sleepSession : sleepSessions) {
|
||||
result += sleepSession.getLightSleepDuration();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private long calculateDeepSleepDuration(List<SleepSession> sleepSessions) {
|
||||
long result = 0;
|
||||
for (SleepSession sleepSession : sleepSessions) {
|
||||
result += sleepSession.getDeepSleepDuration();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateChartsnUIThread(ChartsData chartsData) {
|
||||
MyChartsData mcd = (MyChartsData) chartsData;
|
||||
mSleepAmountChart.setCenterText(mcd.getPieData().getTotalSleep());
|
||||
mSleepAmountChart.setData(mcd.getPieData().getPieData());
|
||||
MySleepChartsData pieData = mcd.getPieData();
|
||||
mSleepAmountChart.setCenterText(pieData.getTotalSleep());
|
||||
mSleepAmountChart.setData(pieData.getPieData());
|
||||
|
||||
mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
|
||||
mActivityChart.getXAxis().setValueFormatter(mcd.getChartsData().getXValueFormatter());
|
||||
mActivityChart.setData(mcd.getChartsData().getData());
|
||||
|
||||
if (mcd.getPieData().getStartSleep() != null && mcd.getPieData().getEndSleep() != null) {
|
||||
mSleepchartInfo.setText(getContext().getString(
|
||||
R.string.you_slept,
|
||||
DateTimeUtils.timeToString(mcd.getPieData().getStartSleep()),
|
||||
DateTimeUtils.timeToString(mcd.getPieData().getEndSleep())));
|
||||
|
||||
mSleepchartInfo.setText(buildYouSleptText(pieData));
|
||||
}
|
||||
|
||||
private String buildYouSleptText(MySleepChartsData pieData) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
if (pieData.getSleepSessions().isEmpty()) {
|
||||
result.append(getContext().getString(R.string.you_did_not_sleep));
|
||||
} else {
|
||||
mSleepchartInfo.setText(getContext().getString(R.string.you_did_not_sleep));
|
||||
for (SleepSession sleepSession : pieData.getSleepSessions()) {
|
||||
result.append(getContext().getString(
|
||||
R.string.you_slept,
|
||||
DateTimeUtils.timeToString(sleepSession.getSleepStart()),
|
||||
DateTimeUtils.timeToString(sleepSession.getSleepEnd())));
|
||||
result.append('\n');
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -269,21 +312,19 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
|
||||
@Override
|
||||
protected void renderCharts() {
|
||||
mActivityChart.animateX(ANIM_TIME, Easing.EasingOption.EaseInOutQuart);
|
||||
mActivityChart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
|
||||
mSleepAmountChart.invalidate();
|
||||
}
|
||||
|
||||
private static class MySleepChartsData extends ChartsData {
|
||||
private String totalSleep;
|
||||
private final PieData pieData;
|
||||
private @Nullable Date startSleep;
|
||||
private @Nullable Date endSleep;
|
||||
private final List<SleepSession> sleepSessions;
|
||||
|
||||
public MySleepChartsData(String totalSleep, PieData pieData, @Nullable Date startSleep, @Nullable Date endSleep) {
|
||||
public MySleepChartsData(String totalSleep, PieData pieData, List<SleepSession> sleepSessions) {
|
||||
this.totalSleep = totalSleep;
|
||||
this.pieData = pieData;
|
||||
this.startSleep = startSleep;
|
||||
this.endSleep = endSleep;
|
||||
this.sleepSessions = sleepSessions;
|
||||
}
|
||||
|
||||
public PieData getPieData() {
|
||||
@ -294,14 +335,8 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
return totalSleep;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Date getStartSleep() {
|
||||
return startSleep;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Date getEndSleep() {
|
||||
return endSleep;
|
||||
public List<SleepSession> getSleepSessions() {
|
||||
return sleepSessions;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
/* Copyright (C) 2015-2019 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Vebryn
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2018 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2019 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -16,8 +16,7 @@
|
||||
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 com.github.mikephil.charting.formatter.ValueFormatter;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
@ -25,7 +24,7 @@ import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
public class TimestampValueFormatter implements IAxisValueFormatter {
|
||||
public class TimestampValueFormatter extends ValueFormatter {
|
||||
private final Calendar cal;
|
||||
// private DateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
|
||||
private DateFormat dateFormat;
|
||||
@ -42,7 +41,7 @@ public class TimestampValueFormatter implements IAxisValueFormatter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedValue(float value, AxisBase axis) {
|
||||
public String getFormattedValue(float value) {
|
||||
cal.setTimeInMillis((int) value * 1000L);
|
||||
Date date = cal.getTime();
|
||||
String dateString = dateFormat.format(date);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2018 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2019 Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2017-2018 Andreas Shimokawa, Carsten Pfeiffer, Pavel Elagin
|
||||
/* Copyright (C) 2017-2019 Andreas Shimokawa, Carsten Pfeiffer, Pavel
|
||||
Elagin, vanous
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -17,13 +18,9 @@
|
||||
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.Legend;
|
||||
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 com.github.mikephil.charting.formatter.ValueFormatter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -40,7 +37,12 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return getString(R.string.weeksleepchart_sleep_a_week);
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
return getString(R.string.weeksleepchart_sleep_a_month);
|
||||
}
|
||||
else{
|
||||
return getString(R.string.weeksleepchart_sleep_a_week);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -74,7 +76,7 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
||||
@Override
|
||||
protected String getBalanceMessage(long balance, int targetValue) {
|
||||
if (balance > 0) {
|
||||
final long totalBalance = balance - (targetValue * TOTAL_DAYS);
|
||||
final long totalBalance = balance - ((long)targetValue * TOTAL_DAYS);
|
||||
if (totalBalance > 0)
|
||||
return getString(R.string.overslept, getHM(totalBalance));
|
||||
else
|
||||
@ -110,30 +112,30 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
IValueFormatter getPieValueFormatter() {
|
||||
return new IValueFormatter() {
|
||||
ValueFormatter getPieValueFormatter() {
|
||||
return new ValueFormatter() {
|
||||
@Override
|
||||
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
|
||||
public String getFormattedValue(float value) {
|
||||
return formatPieValue((long) value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
IValueFormatter getBarValueFormatter() {
|
||||
return new IValueFormatter() {
|
||||
ValueFormatter getBarValueFormatter() {
|
||||
return new ValueFormatter() {
|
||||
@Override
|
||||
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
|
||||
public String getFormattedValue(float value) {
|
||||
return DateTimeUtils.minutesToHHMM((int) value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
IAxisValueFormatter getYAxisFormatter() {
|
||||
return new IAxisValueFormatter() {
|
||||
ValueFormatter getYAxisFormatter() {
|
||||
return new ValueFormatter() {
|
||||
@Override
|
||||
public String getFormattedValue(float value, AxisBase axis) {
|
||||
public String getFormattedValue(float value) {
|
||||
return DateTimeUtils.minutesToHHMM((int) value);
|
||||
}
|
||||
};
|
||||
@ -167,4 +169,10 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
||||
private String getHM(long value) {
|
||||
return DateTimeUtils.formatDurationHoursMinutes(value, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getAverage(float value) {
|
||||
return getHM((long)value);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti
|
||||
/* Copyright (C) 2015-2019 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Pavel Elagin, vanous
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -18,8 +18,7 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||
|
||||
import com.github.mikephil.charting.charts.Chart;
|
||||
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
|
||||
import com.github.mikephil.charting.formatter.IValueFormatter;
|
||||
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -30,7 +29,12 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
public class WeekStepsChartFragment extends AbstractWeekChartFragment {
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return getString(R.string.weekstepschart_steps_a_week);
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
return getString(R.string.weekstepschart_steps_a_month);
|
||||
}
|
||||
else{
|
||||
return getString(R.string.weekstepschart_steps_a_week);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -77,17 +81,17 @@ public class WeekStepsChartFragment extends AbstractWeekChartFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
IValueFormatter getPieValueFormatter() {
|
||||
ValueFormatter getPieValueFormatter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
IValueFormatter getBarValueFormatter() {
|
||||
ValueFormatter getBarValueFormatter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
IAxisValueFormatter getYAxisFormatter() {
|
||||
ValueFormatter getYAxisFormatter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -105,7 +109,7 @@ public class WeekStepsChartFragment extends AbstractWeekChartFragment {
|
||||
@Override
|
||||
protected String getBalanceMessage(long balance, int targetValue) {
|
||||
if (balance > 0) {
|
||||
final long totalBalance = balance - (targetValue * TOTAL_DAYS);
|
||||
final long totalBalance = balance - ((long)targetValue * TOTAL_DAYS);
|
||||
if (totalBalance > 0)
|
||||
return getString(R.string.overstep, Math.abs(totalBalance));
|
||||
else
|
||||
@ -113,4 +117,9 @@ public class WeekStepsChartFragment extends AbstractWeekChartFragment {
|
||||
} else
|
||||
return getString(R.string.no_data);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getAverage(float value) {
|
||||
return String.format("%.0f", value);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,78 @@
|
||||
/* Copyright (C) 2019 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.devicesettings;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
|
||||
public class DeviceSettingsActivity extends AbstractGBActivity implements
|
||||
PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeviceSettingsActivity.class);
|
||||
|
||||
GBDevice device;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_device_settings);
|
||||
if (savedInstanceState == null) {
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(DeviceSpecificSettingsFragment.FRAGMENT_TAG);
|
||||
if (fragment == null) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
fragment = DeviceSpecificSettingsFragment.newInstance(device.getAddress(), coordinator.getSupportedDeviceSpecificSettings(device));
|
||||
}
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings_container, fragment, DeviceSpecificSettingsFragment.FRAGMENT_TAG)
|
||||
.commit();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen preferenceScreen) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
|
||||
PreferenceFragmentCompat fragment = DeviceSpecificSettingsFragment.newInstance(device.getAddress(), coordinator.getSupportedDeviceSpecificSettings(device));
|
||||
Bundle args = fragment.getArguments();
|
||||
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
|
||||
fragment.setArguments(args);
|
||||
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings_container, fragment, preferenceScreen.getKey())
|
||||
.addToBackStack(preferenceScreen.getKey())
|
||||
.commit();
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/* Copyright (C) 2019 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.devicesettings;
|
||||
|
||||
public class DeviceSettingsPreferenceConst {
|
||||
public static final String PREF_DATEFORMAT = "dateformat";
|
||||
public static final String PREF_TIMEFORMAT = "timeformat";
|
||||
public static final String PREF_WEARLOCATION = "wearlocation";
|
||||
public static final String PREF_SCREEN_ORIENTATION = "screen_orientation";
|
||||
public static final String PREF_RESERVER_ALARMS_CALENDAR = "reserve_alarms_calendar";
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user