1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-28 21:06:50 +01:00

Merge branch 'master' into patch-2

This commit is contained in:
Carsten Pfeiffer 2018-08-18 13:16:47 +02:00 committed by GitHub
commit fabe9834e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
553 changed files with 16426 additions and 2453 deletions

View File

@ -16,10 +16,10 @@ android:
- tools - tools
# The BuildTools version used by your project # The BuildTools version used by your project
- build-tools-26.0.2 - build-tools-27.0.3
# The SDK version used to compile your project # The SDK version used to compile your project
- android-25 - android-27
# Additional components # Additional components
- extra-android-m2repository - extra-android-m2repository
@ -30,4 +30,9 @@ android:
#- sys-img-armeabi-v7a-android-19 #- sys-img-armeabi-v7a-android-19
#- sys-img-x86-android-17 #- sys-img-x86-android-17
script: ./gradlew build connectedCheck --stacktrace before_install:
- yes | sdkmanager "platforms;android-27"
script:
- ./gradlew build connectedCheck --stacktrace
- bash config/travis/validate_fastlane_metadata.sh

View File

@ -1,5 +1,116 @@
### Changelog ### Changelog
#### Version 0.29.0
* New Device: Initial support for ID115
* New Device: Initial support for Lenovo Watch9
* Show splash screen during startup
* Vertically align device icon in main activity
* Try to support the google clock application (untested)
* Amazfit Cor: Allow to configure displayed menu items
* Amazfit Cor: Support basic music control
* Amazfit Cor: Fix flashing font files
* Amazfit Bip: improved GPX export
* Amazfit Bip: Fix exported GPX file names for *FAT storage
* Amazfit Bip: Fix current weather not being displayed with later firmwares
* Amazfit Bip/Cor: Try to fix device being sometimes stuck in connecting state
* Mi Band 2: Put some device specific settings into its own settings category
* Mi Band 3: Support disabling of on-device menu items
* Mi Band 3: Support locking the Mi Band sceen (swipe up to unlock)
* Mi Band 2/3: New icon
* NO1 F1: Set time during initialization
#### Version 0.28.1
* Fix wrong weather icon mapping in rare cases
* Fix device discovery on Android 4.4
* Amazfit Bip: Use UTC in gpx tracks for better compatibility with external software
* Amazfit Bip: Add the (localized) activity type to the gpx filename
* Amazfit Bip: Fix weather on latest firmwares
#### Version 0.28.0
* Initial support for ZeTime: time, weather and activity data sync, notification support and music playback control is working
* Amazfit Bip/Cor: Rework firmware detection to cope with new version scheme
* Amazfit Bip: Support setting language to Russian
* Amazfit Cor: Support language switching on newer firmwares
* Mi Band 3: support setting language (english and spanish tested)
* Mi Band 3: Fix pairing
* Mi Band 3: Send AQI to enable display of current temperature
#### Version 0.27.1
* Pebble: Change appstore search to point to RomanPort's pebble appstore
* Mi Band 3: Allow flashing fonts (untested)
* Amazfit Bip: Allow flashing latest firmwares
* Amazfit Cor: Allow flashing Bip fonts (untested)
* Allow to limit auto fetch to a user configurable time interval
#### Version 0.27.0
* Initial support for Mi Band 3 (largely untested, needs to be connected to Mi Fit once)
* Option for automatic activity sync after screen unlock
* Allow hiding activity transfer notification on Android Oreo and above
* Allow blacklisting of pebblekit notifications for individual apps
* Allow blacklisting all application at once
* Forward Skype notifications to wearable even if "local only" flag is set
* Show Gadgetbridge logo behind cards in main activity
* Always stop BT/BTLE discovery when exiting the discovery activity
* Amazfit Bip/Cor: Fix scheduled setting for "display on lift wrist" preference
* Amazfit Bip/Cor: add recent firmwares to whitelist
* Pebble: Fix a rare crash in webview
#### Version 0.26.5
* Fix autoreconnect at boot on recent Android versions
* Bluetooth connection is more stable on Oreo
* Potentially fix the watch continuously vibrating after call pickup
* Amazfit Bip: Add setting to configure shortcuts (swipe to right from watchface)
* Recognize Q8 as a HPlus device
#### Version 0.26.4
* Fix a bug with Toasts appearing every time a notification arrives when bluetooth is disabled
* Pebble 2: Add optional GATT client only mode that might help with connection stability
* Amazfit Cor: Fix detection of newer firmwares
* Mi Band 2: Fix text notifcations not appearing with short vibration patterns
#### Version 0.26.3
* Amazfit Bip: Add proper mime type to shared gpx files
* Amazfit Bip: allow to set displayed menu items
* Amazfit Bip: fix fetching logs from device via debug menu
* Amazfit Bip: Raise .res limit to 700000 bytes for modded files
#### Version 0.26.2
* Amazfit Bip: Time and timezone fixes for Android <=6 when exporting GPX
#### Version 0.26.1
* Fix crashes and connection problems on Android 6 and lower
#### Version 0.26.0
* Amazfit Bip: Initial support for GPS tracks
* Pebble: Wind speed/direction support and bugfixes for weather when using background javascript
#### Version 0.25.1
* Amazfit Cor: Try to send weather location instead of AQI
* Amazfit Bip: Support setting start end end time for background light when lifting the arm
* Pebble: various fixes and improvements for background javascript
* Explicitly ask for RECEIVE_SMS permission to fix problems with Android 8
#### Version 0.25.0
* Initial support for Xwatch
* Move the connected device to top in control center
* Add adaptive launcher icon for Android 8.x
* No longer plot heart rate graph when device was detected as not worn
* Pebble: Small fixes for background js (e.g. Pebble-Casio-WV58DE)
* Pebble: native (non bg js) support for weather in Simply Light watchface
#### Version 0.24.6
* Display the chat icon for notifications coming from Kontalk and Antox
* Pebble: Fix for background js which try to send floats (e.g. TrekVolle)
* Mi Band 2: Change the way vibration patterns work, also fixes problems with missing text on newer firmwares
#### Version 0.24.5
* Fix crash in settings activity with export location
* Fix notification deletion regression
* Add 'Ł' and 'ł' to transliteration map
* Omnijaws Weather: correctly pick today's min and max temperature
* Fix alarm details activity on small screen
* Pebble: mimic online check of TrekVolle when using background js
#### Version 0.24.4 #### Version 0.24.4
* Amazfit Bip: Fix language setting on new firmwares * Amazfit Bip: Fix language setting on new firmwares
@ -55,7 +166,7 @@
* Mi Band 2/Bip/Cor: Whole day HR support * Mi Band 2/Bip/Cor: Whole day HR support
* Mi Band 2/Bip/Cor: Prevent writing a lot of HR samples to the database when not using the live activity feature * Mi Band 2/Bip/Cor: Prevent writing a lot of HR samples to the database when not using the live activity feature
* Pebble: Fix some nasty crashes which occur since 0.22.0 * Pebble: Fix some nasty crashes which occur since 0.22.0
* Workround for non-working notifcations from wechat and outlook * Workaround for non-working notifications from wechat and outlook
#### Version 0.22.3 #### Version 0.22.3
* Amazfit Bip: Allow flashing watchfaces * Amazfit Bip: Allow flashing watchfaces
@ -77,7 +188,7 @@
* Add experimental support for Amazfit Cor and Mi Band HRX (no firmware update on the latter) * Add experimental support for Amazfit Cor and Mi Band HRX (no firmware update on the latter)
* Mi Band 2: Support more icons and textual notifications for more apps * Mi Band 2: Support more icons and textual notifications for more apps
* Add some quick action buttons to Gadgetbridge's notification * Add some quick action buttons to Gadgetbridge's notification
* Add transliteration support for ukranian cyrillic charaters * Add transliteration support for ukrainian cyrillic characters
* Fix annoying toast in Mi Band settings * Fix annoying toast in Mi Band settings
#### Version 0.21.6 #### Version 0.21.6
@ -124,7 +235,7 @@
* Amazfit Bip: Fix call notification with unknown caller * Amazfit Bip: Fix call notification with unknown caller
* Amazfit Bip: Fix crash when weather is updated and device reconnecting * Amazfit Bip: Fix crash when weather is updated and device reconnecting
* Mi2/Bip: Fix crash when synchronizing calendar to alarms * Mi2/Bip: Fix crash when synchronizing calendar to alarms
* Pebble: Fix crash when takeing screenshots on Android 8.0 (Oreo) * Pebble: Fix crash when taking screenshots on Android 8.0 (Oreo)
* Pebble: Support some google app icons * Pebble: Support some google app icons
* Pebble: try to support spotify * Pebble: try to support spotify
* Mi Band 2: Support configurable button actions * Mi Band 2: Support configurable button actions
@ -143,7 +254,7 @@
* Mi Band: Fix setting smart alarms * Mi Band: Fix setting smart alarms
#### Version 0.20.0 #### Version 0.20.0
* Inital Amazfit Bip support (WIP) * Initial Amazfit Bip support (WIP)
* Various theming fixes * Various theming fixes
* Add workaround for blacklist not properly persisting * Add workaround for blacklist not properly persisting
* Handle resetting language to default properly * Handle resetting language to default properly
@ -214,7 +325,7 @@
* Mi Band 2: Fix crash on "chat" or "social network" text notification (#603) * Mi Band 2: Fix crash on "chat" or "social network" text notification (#603)
#### Version 0.18.1 #### Version 0.18.1
* Pebble: Fix Firmware insstallation on Pebble Time Round (broken since 0.16.0) * Pebble: Fix Firmware installation on Pebble Time Round (broken since 0.16.0)
* Start VibrationActivity when using "find device" button with Vibratissimo * Start VibrationActivity when using "find device" button with Vibratissimo
* Support material fork of K9 * Support material fork of K9
@ -256,9 +367,9 @@
#### Version 0.17.3 #### Version 0.17.3
* HPlus: Improve display of new messages and phone calls * HPlus: Improve display of new messages and phone calls
* HPlus: Fix bug related to steps and heart rate * HPlus: Fix bug related to steps and heart rate
* Pebble: Support dynamic keys for natively supported watchfaces and watchapps (more stability accross versions) * Pebble: Support dynamic keys for natively supported watchfaces and watchapps (more stability across versions)
* Pebble: Fix error Toast being displayed when TimeStyle watchface is not installed * Pebble: Fix error Toast being displayed when TimeStyle watchface is not installed
* Mi Band 1+2: Support for connecting wihout BT pairing (workaround for certain connection problems) * Mi Band 1+2: Support for connecting without BT pairing (workaround for certain connection problems)
#### Version 0.17.2 #### Version 0.17.2
* Pebble: Fix temperature unit in Timestyle Pebble watchface * Pebble: Fix temperature unit in Timestyle Pebble watchface
@ -278,7 +389,7 @@
* Pebble: Add option to disable call display * Pebble: Add option to disable call display
* Pebble: Add option to automatically delete notifications that got dismissed on the phone * Pebble: Add option to automatically delete notifications that got dismissed on the phone
* Pebble: Bugfix for some PebbleKit enabled 3rd party apps (TCW and maybe other) * Pebble: Bugfix for some PebbleKit enabled 3rd party apps (TCW and maybe other)
* Pebble 2/LE: Improve reliablitly and transfer speed * Pebble 2/LE: Improve reliability and transfer speed
* HPlus: Improved discovery and pairing * HPlus: Improved discovery and pairing
* HPlus: Improved notifications (display + vibration) * HPlus: Improved notifications (display + vibration)
* HPlus: Synchronize time and date * HPlus: Synchronize time and date
@ -807,7 +918,7 @@
#### Version 0.1.4 #### Version 0.1.4
* New AppManager shows installed Apps/Watchfaces (removal possible via context menu) * New AppManager shows installed Apps/Watchfaces (removal possible via context menu)
* Allow back navigation in ActionBar (Debug and AppMananger Activities) * Allow back navigation in ActionBar (Debug and AppManager Activities)
* Make sure Intent broadcasts do not leave Gadgetbridge * Make sure Intent broadcasts do not leave Gadgetbridge
* Show hint in the Main Activity (tap to connect etc) * Show hint in the Main Activity (tap to connect etc)

View File

@ -2,7 +2,7 @@
names () names ()
{ {
echo -e "\n exit;\n**Contributors (sorted by number of commits):**\n"; echo -e "\n exit;\n**Contributors (sorted by number of commits):**\n";
git log --format='%aN:%ae' origin/master | grep -Ev "FYG_.*_bot_ignore_me" | sed 's/@users.github.com/@users.noreply.github.com/g' | awk 'BEGIN{FS=":"}{ct[$1]+=1;if (length($2) > length(e[$1])) {e[$1]=$2}}END{for (i in e) { n[i]=e[i];c[i]+=ct[i] }; for (a in e) print c[a]"\t* "a" <"n[a]">";}' | sort -n -r | cut -f 2- git log --format='%aN:%aE' origin/master | grep -Ev "(anonymous:|FYG_.*_bot_ignore_me)" | sed 's/@users.github.com/@users.noreply.github.com/g' | awk 'BEGIN{FS=":"}{ct[$1]+=1;e[$1]=$2}END{for (i in e) { n[i]=e[i];c[i]+=ct[i] }; for (a in e) print c[a]"\t* "a" <"n[a]">";}' | sort -n -r | cut -f 2-
} }
quine () quine ()
{ {
@ -12,7 +12,7 @@
declare -f quine | sed -e 's/^[[:space:]]*/ /'; declare -f quine | sed -e 's/^[[:space:]]*/ /';
echo -e " quine\n"; echo -e " quine\n";
names; names;
echo -e "\nAnd all the Transifex translators, which I cannot automatically list, at the moment.\n\n*To update the contributors list just run this file with bash*" echo -e "\nAnd all the Transifex translators, which I cannot automatically list, at the moment.\n\n*To update the contributors list just run this file with bash. Prefix a name with % in .mailmap to set a contact as preferred*"
} > CONTRIBUTORS.rst; } > CONTRIBUTORS.rst;
exit exit
} }
@ -26,47 +26,124 @@
* Carsten Pfeiffer <cpfeiffer@users.noreply.github.com> * Carsten Pfeiffer <cpfeiffer@users.noreply.github.com>
* Daniele Gobbetti <daniele+github@gobbetti.name> * Daniele Gobbetti <daniele+github@gobbetti.name>
* João Paulo Barraca <jpbarraca@gmail.com> * João Paulo Barraca <jpbarraca@gmail.com>
* ivanovlev <lion.ivanov@gmal.com> * Jonas <jonasdcdm@posteo.net>
* Yaron Shahrabani <sh.yaron@gmail.com>
* postsorino <postsorino@krutt.org>
* protomors <protomors@gmail.com>
* Allan Nordhøy <epost@anotheragency.no>
* 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>
* TaaviE <taavi.eomae+weblate@gmail.com>
* Julien Pivotto <roidelapluie@inuits.eu> * Julien Pivotto <roidelapluie@inuits.eu>
* Taavi Eomäe <taavi.eomae+github@gmail.com>
* Steffen Liebergeld <perl@gmx.org> * Steffen Liebergeld <perl@gmx.org>
* Lem Dulfo <lemuel.dulfo@gmail.com> * 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> * Sergey Trofimov <sarg@sarg.org.ru>
* Robert Barat <rbarat07@gmail.com>
* José Rebelo <joserebelo@outlook.com>
* JohnnySun <bmy001@gmail.com> * JohnnySun <bmy001@gmail.com>
* Uwe Hermann <uwe@hermann-uwe.de> * Uwe Hermann <uwe@hermann-uwe.de>
* Edoardo Rosa <edoardo.rosa90@gmail.com>
* Alberto <albertsal83@gmail.com> * Alberto <albertsal83@gmail.com>
* 0nse <0nse@users.noreply.github.com> * Vladislav Serkov <vladserkoff@protonmail.com>
* Vebryn <vebryn@gmail.com>
* Gilles Émilien MOREL <contact@gilles-morel.fr>
* Gergely Peidl <gergely@peidl.net> * Gergely Peidl <gergely@peidl.net>
* Bożydar <trening302@o2.pl>
* 0nse <0nse@users.noreply.github.com>
* Максим Якимчук <xpinovo@gmail.com>
* Rimas Raguliūnas <rarimas@gmail.com>
* masakoodaa <masakoodaa@protonmail.com>
* Lukas Veneziano <fs@venezilu.de>
* Kompact <joaorafael123@hotmail.com>
* Jasper <jespiex456@hotmail.com>
* Christian Fischer <sw-dev@computerlyrik.de> * Christian Fischer <sw-dev@computerlyrik.de>
* c4ndel4 <hadrian.candela@gmail.com>
* 6arms1leg <m.brnsfld@googlemail.com> * 6arms1leg <m.brnsfld@googlemail.com>
* Zhong Jianxin <azuwis@gmail.com>
* walkjivefly <mark@walkjivefly.com> * walkjivefly <mark@walkjivefly.com>
* Ted Stein <me@tedstein.net>
* NotAFIle <nota@notafile.com>
* Normano64 <per.bergqwist@gmail.com> * Normano64 <per.bergqwist@gmail.com>
* NicoBuntu <nicolas__du95@hotmail.fr>
* nautilusx <mail.ka@mailbox.org>
* Minori Hiraoka (미노리) <minori@mnetwork.co.kr>
* Michal Novotny <mignov@gmail.com>
* mesnevi <shams@airpost.net>
* LL <lu.lecocq@free.fr>
* Jesús <zaagur@gmail.com>
* exit-failure <hakrala@web.de>
* Avamander <Avamander@users.noreply.github.com> * Avamander <Avamander@users.noreply.github.com>
* AnthonyDiGirolamo <anthony.digirolamo@gmail.com>
* Andreas Kromke <Andreas.Kromke@web.de>
* Ⲇⲁⲛⲓ Φi <daniphii@outlook.com> * Ⲇⲁⲛⲓ Φi <daniphii@outlook.com>
* Yar <yaroslav.isakov@gmail.com> * Yar <yaroslav.isakov@gmail.com>
* Yaron Shahrabani <sh.yaron@gmail.com>
* xzovy <caleb@caleb-cooper.net> * xzovy <caleb@caleb-cooper.net>
* xphnx <xphnx@users.noreply.github.com> * xphnx <xphnx@users.noreply.github.com>
* Vitaliy Shuruta <vshuruta@gmail.com>
* Tomer Rosenfeld <tomerosenfeld007@gmail.com>
* Tomas Radej <tradej@redhat.com>
* tiparega <11555126+tiparega@users.noreply.github.com>
* Tarik Sekmen <tarik@ilixi.org> * Tarik Sekmen <tarik@ilixi.org>
* Szymon Tomasz Stefanek <s.stefanek@gmail.com> * Szymon Tomasz Stefanek <s.stefanek@gmail.com>
* Sergio Lopez <slp@sinrega.org>
* Sami Alaoui <4ndroidgeek@gmail.com>
* Roman Plevka <rplevka@redhat.com> * Roman Plevka <rplevka@redhat.com>
* rober <rober@prtl.nodomain.net> * rober <rober@prtl.nodomain.net>
* redking <redking974@gmail.com>
* Quallenauge <Hamsi2k@freenet.de>
* Pavel Motyrev <legioner.r@gmail.com>
* Pascal <pascal.tannich@gmail.com>
* Olexandr Nesterenko <olexn@ukr.net>
* Nicolò Balzarotti <anothersms@gmail.com> * Nicolò Balzarotti <anothersms@gmail.com>
* Natanael Arndt <arndtn@gmail.com> * Natanael Arndt <arndtn@gmail.com>
* Moarc <aldwulf@gmail.com>
* Michal Novak <michal.novak@post.cz>
* michaelneu <git@michaeln.eu>
* McSym28 <McSym28@users.noreply.github.com>
* MaxL <z60loa8qw3umzu3@my10minutemail.com>
* Martin <ritualz@users.noreply.github.com>
* Martin Piatka <chachacha2323@gmail.com>
* Marc Schlaich <marc.schlaich@googlemail.com> * Marc Schlaich <marc.schlaich@googlemail.com>
* Manuel Soler <vg8020@gmail.com>
* Luiz Felipe das Neves Lopes <androidfelipe23@gmail.com>
* Leonardo Amaral <contato@leonardoamaral.com.br>
* lazarosfs <lazarosfs@csd.auth.gr>
* ladbsoft <30509719+ladbsoft@users.noreply.github.com>
* Kristjan Räts <kristjanrats@gmail.com>
* kevlarcade <kevlarcade@gmail.com> * kevlarcade <kevlarcade@gmail.com>
* Kevin Richter <me@kevinrichter.nl> * Kevin Richter <me@kevinrichter.nl>
* Kaz Wolfe <root@kazwolfe.io>
* Kasha <kasha_malaga@hotmail.com> * Kasha <kasha_malaga@hotmail.com>
* Joseph Kim <official.jkim@gmail.com>
* Jan Lolek <janlolek@seznam.cz>
* Jakub Jelínek <jakub.jelinek@gmail.com>
* Ivan <ivan_tizhanin@mail.ru> * Ivan <ivan_tizhanin@mail.ru>
* Hasan Ammar <ammarh@gmail.com> * Hasan Ammar <ammarh@gmail.com>
* Gilles MOREL <contact@gilles-morel.fr> * Gilles MOREL <contact@gilles-morel.fr>
* Gilles Émilien MOREL <Almtesh@users.noreply.github.com> * Gideão Gomes Ferreira <trjctr@gmail.com>
* Gabe Schrecker <gabe@pbrb.co.uk>
* freezed-or-frozen <freezed.or.frozen@gmail.com>
* Frank Slezak <KazWolfe@users.noreply.github.com>
* Davis Mosenkovs <davikovs@gmail.com>
* Daniel Hauck <maill@dhauck.eu> * Daniel Hauck <maill@dhauck.eu>
* criogenic <criogenic@gmail.com>
* Chris Perelstein <chris.perelstein@gmail.com> * Chris Perelstein <chris.perelstein@gmail.com>
* chabotsi <chabotsi+github@chabotsi.fr>
* Carlos Ferreira <calbertoferreira@gmail.com> * Carlos Ferreira <calbertoferreira@gmail.com>
* bucala <marcel.bucala@gmail.com>
* batataspt@gmail.com <batataspt@gmail.com>
* atkyritsis <at.kyritsis@gmail.com> * atkyritsis <at.kyritsis@gmail.com>
* AndrewH <36428679+andrewheadricke@users.noreply.github.com>
* andre <andre.buesgen@yahoo.de> * andre <andre.buesgen@yahoo.de>
* Allen B <28495335+Allen-B1@users.noreply.github.com>
* Alexey Afanasev <avafanasiev@gmail.com> * Alexey Afanasev <avafanasiev@gmail.com>
And all the Transifex translators, which I cannot automatically list, at the moment. And all the Transifex translators, which I cannot automatically list, at the moment.
*To update the contributors list just run this file with bash* *To update the contributors list just run this file with bash. Prefix a name with % in .mailmap to set a contact as preferred*

View File

@ -19,7 +19,7 @@
|Realtime Activity Tracking | NO | NO | YES | YES | YES | |Realtime Activity Tracking | NO | NO | YES | YES | YES |
|Music Control | YES | YES | NO | NO | NO | |Music Control | YES | YES | NO | NO | NO |
|Watchapp/face Installation | YES | YES | NO | NO | YES | |Watchapp/face Installation | YES | YES | NO | NO | YES |
|Firmware Installaton | YES | YES | YES | YES | YES | |Firmware Installation | YES | YES | YES | YES | YES |
|Taking Screenshots | YES | YES | NO | NO | NO | |Taking Screenshots | YES | YES | NO | NO | NO |
|Support Android Companion Apps | YES | YES | NO | NO | NO | |Support Android Companion Apps | YES | YES | NO | NO | NO |

View File

@ -15,6 +15,8 @@
*/ */
package nodomain.freeyourgadget.gadgetbridge.daogen; package nodomain.freeyourgadget.gadgetbridge.daogen;
import java.util.Date;
import de.greenrobot.daogenerator.DaoGenerator; import de.greenrobot.daogenerator.DaoGenerator;
import de.greenrobot.daogenerator.Entity; import de.greenrobot.daogenerator.Entity;
import de.greenrobot.daogenerator.Index; import de.greenrobot.daogenerator.Index;
@ -32,6 +34,7 @@ public class GBDaoGenerator {
private static final String MAIN_PACKAGE = "nodomain.freeyourgadget.gadgetbridge"; private static final String MAIN_PACKAGE = "nodomain.freeyourgadget.gadgetbridge";
private static final String MODEL_PACKAGE = MAIN_PACKAGE + ".model"; private static final String MODEL_PACKAGE = MAIN_PACKAGE + ".model";
private static final String VALID_BY_DATE = MODEL_PACKAGE + ".ValidByDate"; private static final String VALID_BY_DATE = MODEL_PACKAGE + ".ValidByDate";
private static final String ACTIVITY_SUMMARY = MODEL_PACKAGE + ".ActivitySummary";
private static final String OVERRIDE = "@Override"; private static final String OVERRIDE = "@Override";
private static final String SAMPLE_RAW_INTENSITY = "rawIntensity"; private static final String SAMPLE_RAW_INTENSITY = "rawIntensity";
private static final String SAMPLE_STEPS = "steps"; private static final String SAMPLE_STEPS = "steps";
@ -42,7 +45,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
Schema schema = new Schema(17, MAIN_PACKAGE + ".entities"); Schema schema = new Schema(18, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema); Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes); Entity user = addUserInfo(schema, userAttributes);
@ -65,9 +68,14 @@ public class GBDaoGenerator {
addHPlusHealthActivityKindOverlay(schema, user, device); addHPlusHealthActivityKindOverlay(schema, user, device);
addHPlusHealthActivitySample(schema, user, device); addHPlusHealthActivitySample(schema, user, device);
addNo1F1ActivitySample(schema, user, device); addNo1F1ActivitySample(schema, user, device);
addXWatchActivitySample(schema, user, device);
addZeTimeActivitySample(schema, user, device);
addID115ActivitySample(schema, user, device);
addCalendarSyncState(schema, device); addCalendarSyncState(schema, device);
addBipActivitySummary(schema, user, device);
new DaoGenerator().generateAll(schema, "app/src/main/java"); new DaoGenerator().generateAll(schema, "app/src/main/java");
} }
@ -269,6 +277,43 @@ public class GBDaoGenerator {
return activitySample; return activitySample;
} }
private static Entity addXWatchActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "XWatchActivitySample");
activitySample.implementsSerializable();
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
addHeartRateProperties(activitySample);
return activitySample;
}
private static Entity addZeTimeActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "ZeTimeActivitySample");
activitySample.implementsSerializable();
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
addHeartRateProperties(activitySample);
activitySample.addIntProperty("caloriesBurnt");
activitySample.addIntProperty("distanceMeters");
activitySample.addIntProperty("activeTimeMinutes");
return activitySample;
}
private static Entity addID115ActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "ID115ActivitySample");
activitySample.implementsSerializable();
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty("caloriesBurnt");
activitySample.addIntProperty("distanceMeters");
activitySample.addIntProperty("activeTimeMinutes");
return activitySample;
}
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) { private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
activitySample.setSuperclass(superClass); activitySample.setSuperclass(superClass);
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider"); activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
@ -297,6 +342,31 @@ public class GBDaoGenerator {
calendarSyncState.addIntProperty("hash").notNull(); calendarSyncState.addIntProperty("hash").notNull();
} }
private static void addBipActivitySummary(Schema schema, Entity user, Entity device) {
Entity summary = addEntity(schema, "BaseActivitySummary");
summary.implementsInterface(ACTIVITY_SUMMARY);
summary.addIdProperty();
summary.setJavaDoc(
"This class represents the summary of a user's activity event. I.e. a walk, hike, a bicycle tour, etc.");
summary.addStringProperty("name").codeBeforeGetter(OVERRIDE);
summary.addDateProperty("startTime").notNull().codeBeforeGetter(OVERRIDE);
summary.addDateProperty("endTime").notNull().codeBeforeGetter(OVERRIDE);
summary.addIntProperty("activityKind").notNull().codeBeforeGetter(OVERRIDE);
summary.addIntProperty("baseLongitude").javaDocGetterAndSetter("Temporary, bip-specific");
summary.addIntProperty("baseLatitude").javaDocGetterAndSetter("Temporary, bip-specific");
summary.addIntProperty("baseAltitude").javaDocGetterAndSetter("Temporary, bip-specific");
summary.addStringProperty("gpxTrack").codeBeforeGetter(OVERRIDE);
Property deviceId = summary.addLongProperty("deviceId").notNull().codeBeforeGetter(OVERRIDE).getProperty();
summary.addToOne(device, deviceId);
Property userId = summary.addLongProperty("userId").notNull().codeBeforeGetter(OVERRIDE).getProperty();
summary.addToOne(user, userId);
}
private static Property findProperty(Entity entity, String propertyName) { private static Property findProperty(Entity entity, String propertyName) {
for (Property prop : entity.getProperties()) { for (Property prop : entity.getProperties()) {
if (propertyName.equals(prop.getPropertyName())) { if (propertyName.equals(prop.getPropertyName())) {

View File

@ -2,14 +2,14 @@ Gadgetbridge
============ ============
Gadgetbridge is an Android (4.4+) application which will allow you to use your Gadgetbridge is an Android (4.4+) application which will allow you to use your
Pebble, Mi Band, Amazfit Bit and HPlus device (and more) without the vendor's closed source application Pebble, Mi Band, Amazfit Bip and HPlus device (and more) without the vendor's closed source application
and without the need to create an account and transmit any of your data to the and without the need to create an account and transmit any of your data to the
vendor's servers. vendor's servers.
[Homepage](https://gadgetbridge.org) [Homepage](https://gadgetbridge.org)
[Blog](https://blog.gadgetbridge.org) [Blog](https://blog.freeyourgadget.org)
[![Donate](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/Gadgetbridge/donate) [![Donate](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/Gadgetbridge/donate)
@ -22,17 +22,22 @@ vendor's servers.
[List of changes](https://github.com/Freeyourgadget/Gadgetbridge/blob/master/CHANGELOG.md) [List of changes](https://github.com/Freeyourgadget/Gadgetbridge/blob/master/CHANGELOG.md)
## Supported Devices ## Supported Devices
* 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)
* 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)
* Amazfit Bip [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip) * Amazfit Bip [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip)
* Amazfit Cor (no maintainer) [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Cor) * 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) * HPlus Devices (e.g. ZeBand) [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/HPlus)
* Teclast H10, H30 (WIP) * 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) * NO.1 F1 (WIP)
* Liveview * 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)
* XWatch (Affordable Chinese Casio-like smartwatches)
* Vibratissimo (experimental) * Vibratissimo (experimental)
* ZeTime (WIP)
## Features ## Features
@ -91,6 +96,10 @@ For more information read [this wiki article](https://github.com/Freeyourgadget/
* João Paulo Barraca (HPlus) * João Paulo Barraca (HPlus)
* Vitaly Svyastyn (NO.1 F1) * Vitaly Svyastyn (NO.1 F1)
* Sami Alaoui (Teclast H30) * Sami Alaoui (Teclast H30)
* "ladbsoft" (XWatch)
* Sebastian Kranz (ZeTime)
* Vadim Kaushan (ID115)
* "maxirnilian" (Lenovo Watch 9)
## Contribute ## Contribute

View File

@ -1,14 +1,13 @@
apply plugin: "com.android.application"
apply plugin: 'com.android.application' apply plugin: "findbugs"
apply plugin: 'findbugs' apply plugin: "pmd"
apply plugin: 'pmd'
def ABORT_ON_CHECK_FAILURE = false def ABORT_ON_CHECK_FAILURE = false
tasks.withType(Test) { tasks.withType(Test) {
systemProperty 'MiFirmwareDir', System.getProperty('MiFirmwareDir', null) systemProperty "MiFirmwareDir", System.getProperty("MiFirmwareDir", null)
systemProperty 'logback.configurationFile', System.getProperty('user.dir', null) + '/app/src/main/assets/logback.xml' systemProperty "logback.configurationFile", System.getProperty("user.dir", null) + "/app/src/main/assets/logback.xml"
systemProperty 'GB_LOGFILES_DIR', java.nio.file.Files.createTempDirectory('gblog').toString(); systemProperty "GB_LOGFILES_DIR", java.nio.file.Files.createTempDirectory("gblog").toString()
} }
android { android {
@ -17,112 +16,115 @@ android {
sourceCompatibility JavaVersion.VERSION_1_7 sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7
} }
compileSdkVersion 25 compileSdkVersion 27
buildToolsVersion '26.0.2' buildToolsVersion "27.0.3"
defaultConfig { defaultConfig {
applicationId "nodomain.freeyourgadget.gadgetbridge" applicationId "nodomain.freeyourgadget.gadgetbridge"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 25 targetSdkVersion 27
// note: always bump BOTH versionCode and versionName! // Note: always bump BOTH versionCode and versionName!
versionName "0.24.4" versionName "0.29.0"
versionCode 121 versionCode 136
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
} }
} }
lintOptions { lintOptions {
abortOnError ABORT_ON_CHECK_FAILURE abortOnError ABORT_ON_CHECK_FAILURE
lintConfig file("${project.rootDir}/config/lint/lint.xml") lintConfig file("${project.rootDir}/config/lint/lint.xml")
// If true, generate an HTML report (with issue explanations, sourcecode, etc)
// if true, generate an HTML report (with issue explanations, sourcecode, etc)
htmlReport true htmlReport true
// optional path to report (default will be lint-results.html in the builddir) // Optional path to report (default will be lint-results.html in the builddir)
htmlOutput file("$project.buildDir/reports/lint/lint.html") htmlOutput file("$project.buildDir/reports/lint/lint.html")
} }
testOptions { testOptions {
unitTests.returnDefaultValues = true unitTests {
returnDefaultValues = true
includeAndroidResources = true
}
} }
} }
pmd { pmd {
toolVersion = '5.5.5' toolVersion = "5.5.5"
} }
dependencies { dependencies {
// testCompile 'ch.qos.logback:logback-classic:1.1.3' // testImplementation "ch.qos.logback:logback-classic:1.1.3"
// testCompile 'ch.qos.logback:logback-core:1.1.3' // testImplementation "ch.qos.logback:logback-core:1.1.3"
testCompile 'junit:junit:4.12' testImplementation "junit:junit:4.12"
testCompile "org.mockito:mockito-core:1.10.19" testImplementation "org.mockito:mockito-core:1.10.19"
testCompile "org.robolectric:robolectric:3.5.1" testImplementation "org.robolectric:robolectric:3.6.1"
compile fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: "libs", include: ["*.jar"])
compile 'com.android.support:appcompat-v7:25.4.0' implementation "com.android.support:appcompat-v7:27.1.1"
compile 'com.android.support:cardview-v7:25.4.0' implementation "com.android.support:cardview-v7:27.1.1"
compile 'com.android.support:recyclerview-v7:25.4.0' implementation "com.android.support:recyclerview-v7:27.1.1"
compile 'com.android.support:support-v4:25.4.0' implementation "com.android.support:support-v4:27.1.1"
compile 'com.android.support:gridlayout-v7:25.4.0' implementation "com.android.support:gridlayout-v7:27.1.1"
compile 'com.android.support:design:25.4.0' implementation "com.android.support:design:27.1.1"
compile 'com.android.support:palette-v7:25.4.0' implementation "com.android.support:palette-v7:27.1.1"
compile('com.github.tony19:logback-android-classic:1.1.1-6') { implementation("com.github.tony19:logback-android-classic:1.1.1-6") {
exclude group: 'com.google.android', module: 'android' exclude group: "com.google.android", module: "android"
} }
compile 'org.slf4j:slf4j-api:1.7.12' implementation "org.slf4j:slf4j-api:1.7.12"
compile 'com.github.Freeyourgadget:MPAndroidChart:5e5bd6c1d3e95c515d4853647ae554e48ee1d593' implementation "com.github.Freeyourgadget:MPAndroidChart:5e5bd6c1d3e95c515d4853647ae554e48ee1d593"
compile 'com.github.pfichtner:durationformatter:0.1.1' implementation "com.github.pfichtner:durationformatter:0.1.1"
compile 'de.cketti.library.changelog:ckchangelog:1.2.2' implementation "de.cketti.library.changelog:ckchangelog:1.2.2"
compile 'net.e175.klaus:solarpositioning:0.0.9' implementation "net.e175.klaus:solarpositioning:0.0.9"
// use pristine greendao instead of our custom version, since our custom jitpack-packaged // 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. // version contains way too much and our custom patches are in the generator only.
compile 'org.greenrobot:greendao:2.2.1' implementation "org.greenrobot:greendao:2.2.1"
compile 'org.apache.commons:commons-lang3:3.5' implementation "org.apache.commons:commons-lang3:3.5"
compile 'org.cyanogenmod:platform.sdk:6.0' implementation "org.cyanogenmod:platform.sdk:6.0"
// compile project(":DaoCore") // implementation project(":DaoCore")
} }
preBuild.dependsOn(":GBDaoGenerator:genSources") preBuild.dependsOn(":GBDaoGenerator:genSources")
gradle.beforeProject { gradle.beforeProject {
preBuild.dependsOn(":GBDaoGenerator:genSources") preBuild.dependsOn(":GBDaoGenerator:genSources")
} }
check.dependsOn 'findbugs', 'pmd', 'lint' check.dependsOn "findbugs", "pmd", "lint"
task pmd(type: Pmd) { task pmd(type: Pmd) {
ruleSetFiles = files("${project.rootDir}/config/pmd/pmd-ruleset.xml") ruleSetFiles = files("${project.rootDir}/config/pmd/pmd-ruleset.xml")
ignoreFailures = !ABORT_ON_CHECK_FAILURE ignoreFailures = !ABORT_ON_CHECK_FAILURE
ruleSets = [ ruleSets = [
'java-android', "java-android",
'java-basic', "java-basic",
'java-braces', "java-braces",
'java-clone', "java-clone",
'java-codesize', "java-codesize",
'java-controversial', "java-controversial",
'java-coupling', "java-coupling",
'java-design', "java-design",
'java-empty', "java-empty",
'java-finalizers', "java-finalizers",
'java-imports', "java-imports",
'java-junit', "java-junit",
'java-optimizations', "java-optimizations",
'java-strictexception', "java-strictexception",
'java-strings', "java-strings",
'java-sunsecure', "java-sunsecure",
'java-typeresolution', "java-typeresolution",
'java-unnecessary', "java-unnecessary",
'java-unusedcode' "java-unusedcode"
] ]
source 'src' source "src"
include '**/*.java' include "**/*.java"
exclude '**/gen/**' exclude "**/gen/**"
reports { reports {
xml.enabled = false xml.enabled = false
@ -142,7 +144,7 @@ task findbugs(type: FindBugs) {
reportLevel = "medium" reportLevel = "medium"
excludeFilter = new File("${project.rootDir}/config/findbugs/findbugs-filter.xml") excludeFilter = new File("${project.rootDir}/config/findbugs/findbugs-filter.xml")
classes = files("${project.rootDir}/app/build/intermediates/classes") classes = files("${project.rootDir}/app/build/intermediates/classes")
source = fileTree('src/main/java/') source = fileTree("src/main/java/")
classpath = files() classpath = files()
reports { reports {
xml.enabled = false xml.enabled = false

View File

@ -17,6 +17,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CALENDAR" /> <uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission 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.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="cyanogenmod.permission.ACCESS_WEATHER_MANAGER" /> <uses-permission android:name="cyanogenmod.permission.ACCESS_WEATHER_MANAGER" />
@ -37,13 +38,14 @@
android:name=".GBApplication" android:name=".GBApplication"
android:allowBackup="false" android:allowBackup="false"
android:fullBackupContent="false" android:fullBackupContent="false"
android:icon="@drawable/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/GadgetbridgeTheme"> android:theme="@style/GadgetbridgeTheme">
<activity <activity
android:name=".activities.ControlCenterv2" android:name=".activities.ControlCenterv2"
android:label="@string/title_activity_controlcenter" android:label="@string/title_activity_controlcenter"
android:theme="@style/GadgetbridgeTheme.NoActionBar"> android:theme="@style/SplashTheme">
<intent-filter> <intent-filter>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -58,6 +60,10 @@
android:name=".devices.miband.MiBandPreferencesActivity" android:name=".devices.miband.MiBandPreferencesActivity"
android:label="@string/preferences_miband_settings" android:label="@string/preferences_miband_settings"
android:parentActivityName=".activities.SettingsActivity" /> android:parentActivityName=".activities.SettingsActivity" />
<activity
android:name=".activities.ActivitySummariesActivity"
android:label="@string/activity_summaries"
android:parentActivityName=".activities.ControlCenterv2" />
<activity <activity
android:launchMode="singleTop" android:launchMode="singleTop"
android:name=".activities.appmanager.AppManagerActivity" android:name=".activities.appmanager.AppManagerActivity"
@ -381,6 +387,12 @@
<activity <activity
android:name=".devices.pebble.PebblePairingActivity" android:name=".devices.pebble.PebblePairingActivity"
android:label="@string/title_activity_pebble_pairing" /> android:label="@string/title_activity_pebble_pairing" />
<activity
android:name=".devices.watch9.Watch9PairingActivity"
android:label="@string/title_activity_watch9_pairing" />
<activity
android:name=".devices.watch9.Watch9CalibrationActivity"
android:label="@string/title_activity_watch9_calibration" />
<activity <activity
android:name=".activities.charts.ChartsActivity" android:name=".activities.charts.ChartsActivity"
android:label="@string/title_activity_charts" android:label="@string/title_activity_charts"
@ -414,7 +426,7 @@
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data
android:name="android.support.FILE_PROVIDER_PATHS" android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/screenshot_provider_paths"/> android:resource="@xml/shared_paths" />
</provider> </provider>
<receiver android:name=".SleepAlarmWidget"> <receiver android:name=".SleepAlarmWidget">

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Normano64 Gobbetti, Martin, Normano64, Taavi Eomäe
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -19,11 +19,17 @@ package nodomain.freeyourgadget.gadgetbridge;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Application; import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.NotificationManager.Policy; import android.app.NotificationManager.Policy;
import android.bluetooth.BluetoothAdapter;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.database.Cursor; import android.database.Cursor;
@ -52,6 +58,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper; import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster; import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothStateChangeReceiver;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
@ -64,6 +71,8 @@ import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_ID;
/** /**
* Main Application class that initializes and provides access to certain things like * Main Application class that initializes and provides access to certain things like
* logging and DB access. * logging and DB access.
@ -109,6 +118,7 @@ public class GBApplication extends Application {
private static Locale language; private static Locale language;
private DeviceManager deviceManager; private DeviceManager deviceManager;
private BluetoothStateChangeReceiver bluetoothStateChangeReceiver;
public static void quit() { public static void quit() {
GB.log("Quitting Gadgetbridge...", GB.INFO, null); GB.log("Quitting Gadgetbridge...", GB.INFO, null);
@ -166,12 +176,25 @@ public class GBApplication extends Application {
setLanguage(language); setLanguage(language);
deviceService = createDeviceService(); deviceService = createDeviceService();
loadAppsBlackList(); loadAppsNotifBlackList();
loadAppsPebbleBlackList();
loadCalendarsBlackList(); loadCalendarsBlackList();
if (isRunningMarshmallowOrLater()) { if (isRunningMarshmallowOrLater()) {
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
//the following will ensure the notification manager is kept alive //the following will ensure the notification manager is kept alive
if(isRunningOreoOrLater()) {
NotificationChannel channel = notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID);
if(channel == null) {
channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
getString(R.string.notification_channel_name),
NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(channel);
}
bluetoothStateChangeReceiver = new BluetoothStateChangeReceiver();
registerReceiver(bluetoothStateChangeReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
}
startService(new Intent(this, NotificationCollectorMonitorService.class)); startService(new Intent(this, NotificationCollectorMonitorService.class));
} }
} }
@ -289,6 +312,13 @@ public class GBApplication extends Application {
public static boolean isRunningMarshmallowOrLater() { public static boolean isRunningMarshmallowOrLater() {
return VERSION.SDK_INT >= Build.VERSION_CODES.M; return VERSION.SDK_INT >= Build.VERSION_CODES.M;
} }
public static boolean isRunningNougatOrLater() {
return VERSION.SDK_INT >= Build.VERSION_CODES.N;
}
public static boolean isRunningOreoOrLater(){
return VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
private static boolean isPrioritySender(int prioritySenders, String number) { private static boolean isPrioritySender(int prioritySenders, String number) {
if (prioritySenders == Policy.PRIORITY_SENDERS_ANY) { if (prioritySenders == Policy.PRIORITY_SENDERS_ANY) {
@ -343,56 +373,117 @@ public class GBApplication extends Application {
return NotificationManager.INTERRUPTION_FILTER_ALL; return NotificationManager.INTERRUPTION_FILTER_ALL;
} }
private static HashSet<String> apps_blacklist = null; private static HashSet<String> apps_notification_blacklist = null;
public static boolean appIsBlacklisted(String packageName) { public static boolean appIsNotifBlacklisted(String packageName) {
if (apps_blacklist == null) { if (apps_notification_blacklist == null) {
GB.log("appIsBlacklisted: apps_blacklist is null!", GB.INFO, null); GB.log("appIsNotifBlacklisted: apps_notification_blacklist is null!", GB.INFO, null);
} }
return apps_blacklist != null && apps_blacklist.contains(packageName); return apps_notification_blacklist != null && apps_notification_blacklist.contains(packageName);
} }
public static void setAppsBlackList(Set<String> packageNames) { public static void setAppsNotifBlackList(Set<String> packageNames) {
if (packageNames == null) { if (packageNames == null) {
GB.log("Set null apps_blacklist", GB.INFO, null); GB.log("Set null apps_notification_blacklist", GB.INFO, null);
apps_blacklist = new HashSet<>(); apps_notification_blacklist = new HashSet<>();
} else { } else {
apps_blacklist = new HashSet<>(packageNames); apps_notification_blacklist = new HashSet<>(packageNames);
} }
GB.log("New apps_blacklist has " + apps_blacklist.size() + " entries", GB.INFO, null); GB.log("New apps_notification_blacklist has " + apps_notification_blacklist.size() + " entries", GB.INFO, null);
saveAppsBlackList(); saveAppsNotifBlackList();
} }
private static void loadAppsBlackList() { private static void loadAppsNotifBlackList() {
GB.log("Loading apps_blacklist", GB.INFO, null); GB.log("Loading apps_notification_blacklist", GB.INFO, null);
apps_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.PACKAGE_BLACKLIST, null); apps_notification_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.PACKAGE_BLACKLIST, null);
if (apps_blacklist == null) { if (apps_notification_blacklist == null) {
apps_blacklist = new HashSet<>(); apps_notification_blacklist = new HashSet<>();
} }
GB.log("Loaded apps_blacklist has " + apps_blacklist.size() + " entries", GB.INFO, null); GB.log("Loaded apps_notification_blacklist has " + apps_notification_blacklist.size() + " entries", GB.INFO, null);
} }
private static void saveAppsBlackList() { private static void saveAppsNotifBlackList() {
GB.log("Saving apps_blacklist with " + apps_blacklist.size() + " entries", GB.INFO, null); GB.log("Saving apps_notification_blacklist with " + apps_notification_blacklist.size() + " entries", GB.INFO, null);
SharedPreferences.Editor editor = sharedPrefs.edit(); SharedPreferences.Editor editor = sharedPrefs.edit();
if (apps_blacklist.isEmpty()) { if (apps_notification_blacklist.isEmpty()) {
editor.putStringSet(GBPrefs.PACKAGE_BLACKLIST, null); editor.putStringSet(GBPrefs.PACKAGE_BLACKLIST, null);
} else { } else {
Prefs.putStringSet(editor, GBPrefs.PACKAGE_BLACKLIST, apps_blacklist); Prefs.putStringSet(editor, GBPrefs.PACKAGE_BLACKLIST, apps_notification_blacklist);
} }
editor.apply(); editor.apply();
} }
public static void addAppToBlacklist(String packageName) { public static void addAppToNotifBlacklist(String packageName) {
if (apps_blacklist.add(packageName)) { if (apps_notification_blacklist.add(packageName)) {
saveAppsBlackList(); saveAppsNotifBlackList();
} }
} }
public static synchronized void removeFromAppsBlacklist(String packageName) { public static synchronized void removeFromAppsNotifBlacklist(String packageName) {
GB.log("Removing from apps_blacklist: " + packageName, GB.INFO, null); GB.log("Removing from apps_notification_blacklist: " + packageName, GB.INFO, null);
apps_blacklist.remove(packageName); apps_notification_blacklist.remove(packageName);
saveAppsBlackList(); saveAppsNotifBlackList();
}
private static HashSet<String> apps_pebblemsg_blacklist = null;
public static boolean appIsPebbleBlacklisted(String sender) {
if (apps_pebblemsg_blacklist == null) {
GB.log("appIsPebbleBlacklisted: apps_pebblemsg_blacklist is null!", GB.INFO, null);
}
return apps_pebblemsg_blacklist != null && apps_pebblemsg_blacklist.contains(sender);
}
public static void setAppsPebbleBlackList(Set<String> packageNames) {
if (packageNames == null) {
GB.log("Set null apps_pebblemsg_blacklist", GB.INFO, null);
apps_pebblemsg_blacklist = new HashSet<>();
} else {
apps_pebblemsg_blacklist = new HashSet<>(packageNames);
}
GB.log("New apps_pebblemsg_blacklist has " + apps_pebblemsg_blacklist.size() + " entries", GB.INFO, null);
saveAppsPebbleBlackList();
}
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);
if (apps_pebblemsg_blacklist == null) {
apps_pebblemsg_blacklist = new HashSet<>();
}
GB.log("Loaded apps_pebblemsg_blacklist has " + apps_pebblemsg_blacklist.size() + " entries", GB.INFO, null);
}
private static void saveAppsPebbleBlackList() {
GB.log("Saving apps_pebblemsg_blacklist with " + apps_pebblemsg_blacklist.size() + " entries", GB.INFO, null);
SharedPreferences.Editor editor = sharedPrefs.edit();
if (apps_pebblemsg_blacklist.isEmpty()) {
editor.putStringSet(GBPrefs.PACKAGE_PEBBLEMSG_BLACKLIST, null);
} else {
Prefs.putStringSet(editor, GBPrefs.PACKAGE_PEBBLEMSG_BLACKLIST, apps_pebblemsg_blacklist);
}
editor.apply();
}
public static void addAppToPebbleBlacklist(String packageName) {
if (apps_pebblemsg_blacklist.add(packageNameToPebbleMsgSender(packageName))) {
saveAppsPebbleBlackList();
}
}
public static synchronized void removeFromAppsPebbleBlacklist(String packageName) {
GB.log("Removing from apps_pebblemsg_blacklist: " + packageName, GB.INFO, null);
apps_pebblemsg_blacklist.remove(packageNameToPebbleMsgSender(packageName));
saveAppsPebbleBlackList();
}
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;
} }
private static HashSet<String> calendars_blacklist = null; private static HashSet<String> calendars_blacklist = null;
@ -406,7 +497,7 @@ public class GBApplication extends Application {
public static void setCalendarsBlackList(Set<String> calendarNames) { public static void setCalendarsBlackList(Set<String> calendarNames) {
if (calendarNames == null) { if (calendarNames == null) {
GB.log("Set null apps_blacklist", GB.INFO, null); GB.log("Set null apps_notification_blacklist", GB.INFO, null);
calendars_blacklist = new HashSet<>(); calendars_blacklist = new HashSet<>();
} else { } else {
calendars_blacklist = new HashSet<>(calendarNames); calendars_blacklist = new HashSet<>(calendarNames);
@ -490,7 +581,7 @@ public class GBApplication extends Application {
case 0: case 0:
String legacyGender = sharedPrefs.getString("mi_user_gender", null); String legacyGender = sharedPrefs.getString("mi_user_gender", null);
String legacyHeight = sharedPrefs.getString("mi_user_height_cm", null); String legacyHeight = sharedPrefs.getString("mi_user_height_cm", null);
String legacyWeigth = sharedPrefs.getString("mi_user_weight_kg", null); String legacyWeight = sharedPrefs.getString("mi_user_weight_kg", null);
String legacyYOB = sharedPrefs.getString("mi_user_year_of_birth", null); String legacyYOB = sharedPrefs.getString("mi_user_year_of_birth", null);
if (legacyGender != null) { if (legacyGender != null) {
int gender = "male".equals(legacyGender) ? 1 : "female".equals(legacyGender) ? 0 : 2; int gender = "male".equals(legacyGender) ? 1 : "female".equals(legacyGender) ? 0 : 2;
@ -501,8 +592,8 @@ public class GBApplication extends Application {
editor.putString(ActivityUser.PREF_USER_HEIGHT_CM, legacyHeight); editor.putString(ActivityUser.PREF_USER_HEIGHT_CM, legacyHeight);
editor.remove("mi_user_height_cm"); editor.remove("mi_user_height_cm");
} }
if (legacyWeigth != null) { if (legacyWeight != null) {
editor.putString(ActivityUser.PREF_USER_WEIGHT_KG, legacyWeigth); editor.putString(ActivityUser.PREF_USER_WEIGHT_KG, legacyWeight);
editor.remove("mi_user_weight_kg"); editor.remove("mi_user_weight_kg");
} }
if (legacyYOB != null) { if (legacyYOB != null) {
@ -591,4 +682,24 @@ public class GBApplication extends Application {
public static Locale getLanguage() { public static Locale getLanguage() {
return language; return language;
} }
public String getVersion() {
try {
return getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_META_DATA).versionName;
} catch (PackageManager.NameNotFoundException e) {
GB.log("Unable to determine Gadgetbridge's version", GB.WARN, e);
return "0.0.0";
}
}
public String getNameAndVersion() {
try {
ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_META_DATA);
return String.format("%s %s", appInfo.name, packageInfo.versionName);
} catch (PackageManager.NameNotFoundException e) {
GB.log("Unable to determine Gadgetbridge's name/version", GB.WARN, e);
return "Gadgetbridge";
}
}
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Taavi Eomäe
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -24,7 +24,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
/** /**
* Provides lowlevel access to the database. * Provides low-level access to the database.
*/ */
public class LockHandler implements DBHandler { public class LockHandler implements DBHandler {

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Carsten Pfeiffer /* Copyright (C) 2016-2018 Carsten Pfeiffer, Daniele Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -30,6 +30,7 @@ import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.encoder.Encoder; import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.encoder.LayoutWrappingEncoder; import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.util.StatusPrinter; import ch.qos.logback.core.util.StatusPrinter;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public abstract class Logging { public abstract class Logging {
public static final String PROP_LOGFILES_DIR = "GB_LOGFILES_DIR"; public static final String PROP_LOGFILES_DIR = "GB_LOGFILES_DIR";
@ -148,7 +149,7 @@ public abstract class Logging {
} }
StringBuilder builder = new StringBuilder(bytes.length * 5); StringBuilder builder = new StringBuilder(bytes.length * 5);
for (byte b : bytes) { for (byte b : bytes) {
builder.append(String.format("0x%2x", b)); builder.append(String.format("0x%02x", b));
builder.append(" "); builder.append(" ");
} }
return builder.toString().trim(); return builder.toString().trim();
@ -156,9 +157,7 @@ public abstract class Logging {
public static void logBytes(Logger logger, byte[] value) { public static void logBytes(Logger logger, byte[] value) {
if (value != null) { if (value != null) {
for (byte b : value) { logger.warn("DATA: " + GB.hexdump(value, 0, value.length));
logger.warn("DATA: " + String.format("0x%2x", b));
}
} }
} }
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Carsten Pfeiffer /* Copyright (C) 2015-2018 Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 0nse, Carsten Pfeiffer /* Copyright (C) 2016-2018 0nse, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Carsten Pfeiffer /* Copyright (C) 2015-2018 Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, walkjivefly Gobbetti, walkjivefly
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -0,0 +1,53 @@
/* Copyright (C) 2017-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.os.Bundle;
import android.widget.ListView;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractItemAdapter;
public abstract class AbstractListActivity<T> extends AbstractGBActivity {
private AbstractItemAdapter<T> itemAdapter;
private ListView itemListView;
public void setItemAdapter(AbstractItemAdapter<T> itemAdapter) {
this.itemAdapter = itemAdapter;
itemListView.setAdapter(itemAdapter);
}
protected void refresh() {
this.itemAdapter.loadItems();
}
public AbstractItemAdapter<T> getItemAdapter() {
return itemAdapter;
}
public ListView getItemListView() {
return itemListView;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list);
itemListView = findViewById(R.id.itemListView);
}
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Christian /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Christian
Fischer, Daniele Gobbetti, Lem Dulfo Fischer, Daniele Gobbetti, Lem Dulfo
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -0,0 +1,301 @@
/* Copyright (C) 2017-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.DatePickerDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
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;
import android.view.MenuItem;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.DatePicker;
import android.widget.ListView;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class ActivitySummariesActivity extends AbstractListActivity<BaseActivitySummary> {
private GBDevice mGBDevice;
private SwipeRefreshLayout swipeLayout;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (Objects.requireNonNull(action)) {
case GBDevice.ACTION_DEVICE_CHANGED:
GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
mGBDevice = device;
if (device.isBusy()) {
swipeLayout.setRefreshing(true);
} else {
boolean wasBusy = swipeLayout.isRefreshing();
swipeLayout.setRefreshing(false);
if (wasBusy) {
refresh();
}
}
break;
}
}
};
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.activity_list_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
boolean processed = false;
switch (item.getItemId()) {
case R.id.activity_action_manage_timestamp:
resetFetchTimestampToChosenDate();
processed = true;
break;
}
return processed;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Bundle extras = getIntent().getExtras();
if (extras != null) {
mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
} else {
throw new IllegalArgumentException("Must provide a device when invoking this activity");
}
IntentFilter filterLocal = new IntentFilter();
filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
super.onCreate(savedInstanceState);
setItemAdapter(new ActivitySummariesAdapter(this, mGBDevice));
getItemListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Object item = parent.getItemAtPosition(position);
if (item != null) {
ActivitySummary summary = (ActivitySummary) item;
String gpxTrack = summary.getGpxTrack();
if (gpxTrack != null) {
showTrack(gpxTrack);
} else {
GB.toast("This activity does not contain GPX tracks.", Toast.LENGTH_LONG, GB.INFO);
}
}
}
});
getItemListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
getItemListView().setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
@Override
public void onItemCheckedStateChanged(ActionMode actionMode, int position, long id, boolean checked) {
final int selectedItems = getItemListView().getCheckedItemCount();
actionMode.setTitle(selectedItems + " selected");
}
@Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
getMenuInflater().inflate(R.menu.activity_list_context_menu, menu);
findViewById(R.id.fab).setVisibility(View.INVISIBLE);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
boolean processed = false;
SparseBooleanArray checked = getItemListView().getCheckedItemPositions();
switch (menuItem.getItemId()) {
case R.id.activity_action_delete:
List<BaseActivitySummary> toDelete = new ArrayList<>();
for(int i = 0; i< checked.size(); i++) {
if (checked.valueAt(i)) {
toDelete.add(getItemAdapter().getItem(checked.keyAt(i)));
}
}
deleteItems(toDelete);
processed = true;
break;
case R.id.activity_action_export:
List<String> paths = new ArrayList<>();
for(int i = 0; i< checked.size(); i++) {
if (checked.valueAt(i)) {
BaseActivitySummary item = getItemAdapter().getItem(checked.keyAt(i));
if (item != null) {
ActivitySummary summary = (ActivitySummary) item;
String gpxTrack = summary.getGpxTrack();
if (gpxTrack != null) {
paths.add(gpxTrack);
}
}
}
}
shareMultiple(paths);
processed = true;
break;
case R.id.activity_action_select_all:
for ( int i=0; i < getItemListView().getCount(); i++) {
getItemListView().setItemChecked(i, true);
}
return true; //don't finish actionmode in this case!
default:
break;
}
actionMode.finish();
return processed;
}
@Override
public void onDestroyActionMode(ActionMode actionMode) {
findViewById(R.id.fab).setVisibility(View.VISIBLE);
}
});
swipeLayout = findViewById(R.id.list_activity_swipe_layout);
swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
fetchTrackData();
}
});
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
fetchTrackData();
}
});
}
public void resetFetchTimestampToChosenDate() {
final Calendar currentDate = Calendar.getInstance();
new DatePickerDialog(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;
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();
}
}, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
}
@Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
super.onDestroy();
}
private void deleteItems(List<BaseActivitySummary> items) {
for(BaseActivitySummary item : items) {
item.delete();
getItemAdapter().remove(item);
}
refresh();
}
private void showTrack(String gpxTrack) {
try {
AndroidUtils.viewFile(gpxTrack, Intent.ACTION_VIEW, this);
} catch (IOException e) {
GB.toast(this, "Unable to display GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
}
}
private void fetchTrackData() {
if (mGBDevice.isInitialized() && !mGBDevice.isBusy()) {
GBApplication.deviceService().onFetchRecordedData(RecordedDataTypes.TYPE_GPS_TRACKS);
} else {
swipeLayout.setRefreshing(false);
if (!mGBDevice.isInitialized()) {
GB.toast(this, getString(R.string.device_not_connected), Toast.LENGTH_SHORT, GB.ERROR);
}
}
}
private void shareMultiple(List<String> paths){
ArrayList<Uri> uris = new ArrayList<>();
for(String path: paths){
File file = new File(path);
uris.add(FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".screenshot_provider", file));
}
if(uris.size() > 0) {
final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.setType("application/gpx+xml");
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
startActivity(Intent.createChooser(intent, "SHARE"));
} else {
GB.toast(this, "No selected activity contains a GPX track to share", Toast.LENGTH_SHORT, GB.ERROR);
}
}
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo Gobbetti, Lem Dulfo
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -53,15 +53,15 @@ public class AlarmDetails extends AbstractGBActivity {
alarm = getIntent().getParcelableExtra("alarm"); alarm = getIntent().getParcelableExtra("alarm");
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE); device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
timePicker = (TimePicker) findViewById(R.id.alarm_time_picker); timePicker = findViewById(R.id.alarm_time_picker);
cbSmartWakeup = (CheckedTextView) findViewById(R.id.alarm_cb_smart_wakeup); cbSmartWakeup = findViewById(R.id.alarm_cb_smart_wakeup);
cbMonday = (CheckedTextView) findViewById(R.id.alarm_cb_monday); cbMonday = findViewById(R.id.alarm_cb_monday);
cbTuesday = (CheckedTextView) findViewById(R.id.alarm_cb_tuesday); cbTuesday = findViewById(R.id.alarm_cb_tuesday);
cbWednesday = (CheckedTextView) findViewById(R.id.alarm_cb_wednesday); cbWednesday = findViewById(R.id.alarm_cb_wednesday);
cbThursday = (CheckedTextView) findViewById(R.id.alarm_cb_thursday); cbThursday = findViewById(R.id.alarm_cb_thursday);
cbFriday = (CheckedTextView) findViewById(R.id.alarm_cb_friday); cbFriday = findViewById(R.id.alarm_cb_friday);
cbSaturday = (CheckedTextView) findViewById(R.id.alarm_cb_saturday); cbSaturday = findViewById(R.id.alarm_cb_saturday);
cbSunday = (CheckedTextView) findViewById(R.id.alarm_cb_sunday); cbSunday = findViewById(R.id.alarm_cb_sunday);
cbSmartWakeup.setOnClickListener(new View.OnClickListener() { cbSmartWakeup.setOnClickListener(new View.OnClickListener() {

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo Gobbetti, Lem Dulfo
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -22,6 +22,8 @@ import android.support.v4.app.NavUtils;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView; import android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -64,12 +66,25 @@ public class AppBlacklistActivity extends AbstractGBActivity {
}); });
} }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.app_blacklist_menu, menu);
return super.onCreateOptionsMenu(menu);
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case android.R.id.home: case android.R.id.home:
NavUtils.navigateUpFromSameTask(this); NavUtils.navigateUpFromSameTask(this);
return true; return true;
case R.id.blacklist_all_notif:
appBlacklistAdapter.blacklistAllNotif();
return true;
case R.id.whitelist_all_notif:
appBlacklistAdapter.whitelistAllNotif();
return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Carsten Pfeiffer, Daniele Gobbetti /* Copyright (C) 2017-2018 Carsten Pfeiffer, Daniele Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo Gobbetti, Lem Dulfo
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti Gobbetti, Taavi Eomäe
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -24,7 +24,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Canvas;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@ -42,15 +41,13 @@ import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import de.cketti.library.changelog.ChangeLog; import de.cketti.library.changelog.ChangeLog;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -72,9 +69,7 @@ public class ControlCenterv2 extends AppCompatActivity
} }
private DeviceManager deviceManager; private DeviceManager deviceManager;
private ImageView background;
private List<GBDevice> deviceList;
private GBDeviceAdapterv2 mGBDeviceAdapter; private GBDeviceAdapterv2 mGBDeviceAdapter;
private RecyclerView deviceListView; private RecyclerView deviceListView;
@ -84,7 +79,7 @@ public class ControlCenterv2 extends AppCompatActivity
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
switch (action) { switch (Objects.requireNonNull(action)) {
case GBApplication.ACTION_LANGUAGE_CHANGE: case GBApplication.ACTION_LANGUAGE_CHANGE:
setLanguage(GBApplication.getLanguage(), true); setLanguage(GBApplication.getLanguage(), true);
break; break;
@ -104,10 +99,10 @@ public class ControlCenterv2 extends AppCompatActivity
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_controlcenterv2); setContentView(R.layout.activity_controlcenterv2);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() { fab.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -115,28 +110,29 @@ public class ControlCenterv2 extends AppCompatActivity
} }
}); });
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); DrawerLayout drawer = findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.controlcenter_navigation_drawer_open, R.string.controlcenter_navigation_drawer_close); this, drawer, toolbar, R.string.controlcenter_navigation_drawer_open, R.string.controlcenter_navigation_drawer_close);
drawer.setDrawerListener(toggle); drawer.setDrawerListener(toggle);
toggle.syncState(); toggle.syncState();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this); navigationView.setNavigationItemSelectedListener(this);
//end of material design boilerplate //end of material design boilerplate
deviceManager = ((GBApplication) getApplication()).getDeviceManager(); deviceManager = ((GBApplication) getApplication()).getDeviceManager();
deviceListView = (RecyclerView) findViewById(R.id.deviceListView); deviceListView = findViewById(R.id.deviceListView);
deviceListView.setHasFixedSize(true); deviceListView.setHasFixedSize(true);
deviceListView.setLayoutManager(new LinearLayoutManager(this)); deviceListView.setLayoutManager(new LinearLayoutManager(this));
background = (ImageView) findViewById(R.id.no_items_bg);
deviceList = deviceManager.getDevices(); List<GBDevice> deviceList = deviceManager.getDevices();
mGBDeviceAdapter = new GBDeviceAdapterv2(this, deviceList); mGBDeviceAdapter = new GBDeviceAdapterv2(this, deviceList);
deviceListView.setAdapter(this.mGBDeviceAdapter); deviceListView.setAdapter(this.mGBDeviceAdapter);
/* uncomment to enable fixed-swipe to reveal more actions
ItemTouchHelper swipeToDismissTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback( ItemTouchHelper swipeToDismissTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
ItemTouchHelper.LEFT , ItemTouchHelper.RIGHT) { ItemTouchHelper.LEFT , ItemTouchHelper.RIGHT) {
@Override @Override
@ -167,9 +163,8 @@ public class ControlCenterv2 extends AppCompatActivity
} }
}); });
//uncomment to enable fixed-swipe to reveal more actions swipeToDismissTouchHelper.attachToRecyclerView(deviceListView);
//swipeToDismissTouchHelper.attachToRecyclerView(deviceListView); */
registerForContextMenu(deviceListView); registerForContextMenu(deviceListView);
@ -226,7 +221,7 @@ public class ControlCenterv2 extends AppCompatActivity
@Override @Override
public void onBackPressed() { public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) { if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START); drawer.closeDrawer(GravityCompat.START);
} else { } else {
@ -237,7 +232,7 @@ public class ControlCenterv2 extends AppCompatActivity
@Override @Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) { public boolean onNavigationItemSelected(@NonNull MenuItem item) {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); DrawerLayout drawer = findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START); drawer.closeDrawer(GravityCompat.START);
switch (item.getItemId()) { switch (item.getItemId()) {
@ -253,6 +248,10 @@ public class ControlCenterv2 extends AppCompatActivity
Intent dbIntent = new Intent(this, DbManagementActivity.class); Intent dbIntent = new Intent(this, DbManagementActivity.class);
startActivity(dbIntent); startActivity(dbIntent);
return true; return true;
case R.id.action_blacklist:
Intent blIntent = new Intent(this, AppBlacklistActivity.class);
startActivity(blIntent);
return true;
case R.id.action_quit: case R.id.action_quit:
GBApplication.quit(); GBApplication.quit();
return true; return true;
@ -283,13 +282,6 @@ public class ControlCenterv2 extends AppCompatActivity
} }
private void refreshPairedDevices() { private void refreshPairedDevices() {
List<GBDevice> deviceList = deviceManager.getDevices();
if (deviceList.isEmpty()) {
background.setVisibility(View.VISIBLE);
} else {
background.setVisibility(View.INVISIBLE);
}
mGBDeviceAdapter.notifyDataSetChanged(); mGBDeviceAdapter.notifyDataSetChanged();
} }
@ -309,6 +301,8 @@ public class ControlCenterv2 extends AppCompatActivity
wantedPermissions.add(Manifest.permission.READ_PHONE_STATE); wantedPermissions.add(Manifest.permission.READ_PHONE_STATE);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.PROCESS_OUTGOING_CALLS) == PackageManager.PERMISSION_DENIED) if (ContextCompat.checkSelfPermission(this, Manifest.permission.PROCESS_OUTGOING_CALLS) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.PROCESS_OUTGOING_CALLS); wantedPermissions.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.RECEIVE_SMS);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_DENIED) if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_SMS); wantedPermissions.add(Manifest.permission.READ_SMS);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_DENIED) if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_DENIED)
@ -317,6 +311,11 @@ public class ControlCenterv2 extends AppCompatActivity
wantedPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE); wantedPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED) if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_CALENDAR); wantedPermissions.add(Manifest.permission.READ_CALENDAR);
try {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.MEDIA_CONTENT_CONTROL) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.MEDIA_CONTENT_CONTROL);
} catch (Exception ignored){
}
if (!wantedPermissions.isEmpty()) if (!wantedPermissions.isEmpty())
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[wantedPermissions.size()]), 0); ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[wantedPermissions.size()]), 0);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Alberto, Andreas Shimokawa, Carsten Pfeiffer, /* Copyright (C) 2016-2018 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti Daniele Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Frank Slezak, ivanovlev, Kasha, Lem Dulfo, Steffen Liebergeld Gobbetti, Frank Slezak, ivanovlev, Kasha, Lem Dulfo, Steffen Liebergeld
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -40,18 +40,21 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_ID;
public class DebugActivity extends AbstractGBActivity { public class DebugActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(DebugActivity.class); private static final Logger LOG = LoggerFactory.getLogger(DebugActivity.class);
@ -61,23 +64,12 @@ public class DebugActivity extends AbstractGBActivity {
= "nodomain.freeyourgadget.gadgetbridge.DebugActivity.action.reply"; = "nodomain.freeyourgadget.gadgetbridge.DebugActivity.action.reply";
private Spinner sendTypeSpinner; private Spinner sendTypeSpinner;
private Button sendButton;
private Button incomingCallButton;
private Button outgoingCallButton;
private Button startCallButton;
private Button endCallButton;
private Button testNotificationButton;
private Button setMusicInfoButton;
private Button setTimeButton;
private Button rebootButton;
private Button HeartRateButton;
private Button testNewFunctionalityButton;
private EditText editContent; private EditText editContent;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) { switch (Objects.requireNonNull(intent.getAction())) {
case ACTION_REPLY: { case ACTION_REPLY: {
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
CharSequence reply = remoteInput.getCharSequence(EXTRA_REPLY); CharSequence reply = remoteInput.getCharSequence(EXTRA_REPLY);
@ -85,13 +77,11 @@ public class DebugActivity extends AbstractGBActivity {
GB.toast(context, "got wearable reply: " + reply, Toast.LENGTH_SHORT, GB.INFO); GB.toast(context, "got wearable reply: " + reply, Toast.LENGTH_SHORT, GB.INFO);
break; break;
} }
case DeviceService.ACTION_HEARTRATE_MEASUREMENT: { default:
int hrValue = intent.getIntExtra(DeviceService.EXTRA_HEART_RATE_VALUE, -1); LOG.info("ignoring intent action " + intent.getAction());
GB.toast(DebugActivity.this, "Heart Rate measured: " + hrValue, Toast.LENGTH_LONG, GB.INFO);
break; break;
} }
} }
}
}; };
@Override @Override
@ -105,17 +95,17 @@ public class DebugActivity extends AbstractGBActivity {
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
registerReceiver(mReceiver, filter); // for ACTION_REPLY registerReceiver(mReceiver, filter); // for ACTION_REPLY
editContent = (EditText) findViewById(R.id.editContent); editContent = findViewById(R.id.editContent);
ArrayList<String> spinnerArray = new ArrayList<>(); ArrayList<String> spinnerArray = new ArrayList<>();
for (NotificationType notificationType : NotificationType.values()) { for (NotificationType notificationType : NotificationType.values()) {
spinnerArray.add(notificationType.name()); spinnerArray.add(notificationType.name());
} }
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, spinnerArray); ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, spinnerArray);
sendTypeSpinner = (Spinner) findViewById(R.id.sendTypeSpinner); sendTypeSpinner = findViewById(R.id.sendTypeSpinner);
sendTypeSpinner.setAdapter(spinnerArrayAdapter); sendTypeSpinner.setAdapter(spinnerArrayAdapter);
sendButton = (Button) findViewById(R.id.sendButton); Button sendButton = findViewById(R.id.sendButton);
sendButton.setOnClickListener(new View.OnClickListener() { sendButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -132,7 +122,7 @@ public class DebugActivity extends AbstractGBActivity {
} }
}); });
incomingCallButton = (Button) findViewById(R.id.incomingCallButton); Button incomingCallButton = findViewById(R.id.incomingCallButton);
incomingCallButton.setOnClickListener(new View.OnClickListener() { incomingCallButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -142,7 +132,7 @@ public class DebugActivity extends AbstractGBActivity {
GBApplication.deviceService().onSetCallState(callSpec); GBApplication.deviceService().onSetCallState(callSpec);
} }
}); });
outgoingCallButton = (Button) findViewById(R.id.outgoingCallButton); Button outgoingCallButton = findViewById(R.id.outgoingCallButton);
outgoingCallButton.setOnClickListener(new View.OnClickListener() { outgoingCallButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -153,7 +143,7 @@ public class DebugActivity extends AbstractGBActivity {
} }
}); });
startCallButton = (Button) findViewById(R.id.startCallButton); Button startCallButton = findViewById(R.id.startCallButton);
startCallButton.setOnClickListener(new View.OnClickListener() { startCallButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -162,7 +152,7 @@ public class DebugActivity extends AbstractGBActivity {
GBApplication.deviceService().onSetCallState(callSpec); GBApplication.deviceService().onSetCallState(callSpec);
} }
}); });
endCallButton = (Button) findViewById(R.id.endCallButton); Button endCallButton = findViewById(R.id.endCallButton);
endCallButton.setOnClickListener(new View.OnClickListener() { endCallButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -172,15 +162,15 @@ public class DebugActivity extends AbstractGBActivity {
} }
}); });
rebootButton = (Button) findViewById(R.id.rebootButton); Button rebootButton = findViewById(R.id.rebootButton);
rebootButton.setOnClickListener(new View.OnClickListener() { rebootButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
GBApplication.deviceService().onReboot(); GBApplication.deviceService().onReboot();
} }
}); });
HeartRateButton = (Button) findViewById(R.id.HearRateButton); Button heartRateButton = findViewById(R.id.HeartRateButton);
HeartRateButton.setOnClickListener(new View.OnClickListener() { heartRateButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
GB.toast("Measuring heart rate, please wait...", Toast.LENGTH_LONG, GB.INFO); GB.toast("Measuring heart rate, please wait...", Toast.LENGTH_LONG, GB.INFO);
@ -188,7 +178,7 @@ public class DebugActivity extends AbstractGBActivity {
} }
}); });
setMusicInfoButton = (Button) findViewById(R.id.setMusicInfoButton); Button setMusicInfoButton = findViewById(R.id.setMusicInfoButton);
setMusicInfoButton.setOnClickListener(new View.OnClickListener() { setMusicInfoButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -214,7 +204,7 @@ public class DebugActivity extends AbstractGBActivity {
} }
}); });
setTimeButton = (Button) findViewById(R.id.setTimeButton); Button setTimeButton = findViewById(R.id.setTimeButton);
setTimeButton.setOnClickListener(new View.OnClickListener() { setTimeButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -222,7 +212,7 @@ public class DebugActivity extends AbstractGBActivity {
} }
}); });
testNotificationButton = (Button) findViewById(R.id.testNotificationButton); Button testNotificationButton = findViewById(R.id.testNotificationButton);
testNotificationButton.setOnClickListener(new View.OnClickListener() { testNotificationButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -230,7 +220,15 @@ public class DebugActivity extends AbstractGBActivity {
} }
}); });
testNewFunctionalityButton = (Button) findViewById(R.id.testNewFunctionality); Button fetchDebugLogsButton = findViewById(R.id.fetchDebugLogsButton);
fetchDebugLogsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GBApplication.deviceService().onFetchRecordedData(RecordedDataTypes.TYPE_DEBUGLOGS);
}
});
Button testNewFunctionalityButton = findViewById(R.id.testNewFunctionality);
testNewFunctionalityButton.setOnClickListener(new View.OnClickListener() { testNewFunctionalityButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -266,7 +264,7 @@ public class DebugActivity extends AbstractGBActivity {
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender().addAction(action); NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender().addAction(action);
NotificationCompat.Builder ncomp = new NotificationCompat.Builder(this) NotificationCompat.Builder ncomp = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setContentTitle(getString(R.string.test_notification)) .setContentTitle(getString(R.string.test_notification))
.setContentText(getString(R.string.this_is_a_test_notification_from_gadgetbridge)) .setContentText(getString(R.string.this_is_a_test_notification_from_gadgetbridge))
.setTicker(getString(R.string.this_is_a_test_notification_from_gadgetbridge)) .setTicker(getString(R.string.this_is_a_test_notification_from_gadgetbridge))
@ -275,8 +273,10 @@ public class DebugActivity extends AbstractGBActivity {
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.extend(wearableExtender); .extend(wearableExtender);
if (nManager != null) {
nManager.notify((int) System.currentTimeMillis(), ncomp.build()); nManager.notify((int) System.currentTimeMillis(), ncomp.build());
} }
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
@ -295,7 +295,4 @@ public class DebugActivity extends AbstractGBActivity {
unregisterReceiver(mReceiver); unregisterReceiver(mReceiver);
} }
public interface DeviceSelectionCallback {
void invoke(GBDevice device);
}
} }

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, JohnnySun, Lem Dulfo, Uwe Hermann Gobbetti, JohnnySun, Lem Dulfo, Taavi Eomäe, Uwe Hermann
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -24,6 +24,7 @@ import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanRecord; import android.bluetooth.le.ScanRecord;
@ -54,6 +55,7 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
@ -66,8 +68,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static android.bluetooth.le.ScanSettings.MATCH_MODE_STICKY;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_LATENCY;
public class DiscoveryActivity extends AbstractGBActivity implements AdapterView.OnItemClickListener { public class DiscoveryActivity extends AbstractGBActivity implements AdapterView.OnItemClickListener {
private static final Logger LOG = LoggerFactory.getLogger(DiscoveryActivity.class); private static final Logger LOG = LoggerFactory.getLogger(DiscoveryActivity.class);
@ -80,7 +80,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() { private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) { switch (Objects.requireNonNull(intent.getAction())) {
case BluetoothAdapter.ACTION_DISCOVERY_STARTED: case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
if (isScanning != Scanning.SCANNING_BTLE && isScanning != Scanning.SCANNING_NEW_BTLE) { if (isScanning != Scanning.SCANNING_BTLE && isScanning != Scanning.SCANNING_NEW_BTLE) {
discoveryStarted(Scanning.SCANNING_BT); discoveryStarted(Scanning.SCANNING_BT);
@ -105,9 +105,8 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
}); });
break; break;
case BluetoothAdapter.ACTION_STATE_CHANGED: case BluetoothAdapter.ACTION_STATE_CHANGED:
int oldState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, BluetoothAdapter.STATE_OFF);
int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF); int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
bluetoothStateChanged(oldState, newState); bluetoothStateChanged(newState);
break; break;
case BluetoothDevice.ACTION_FOUND: { case BluetoothDevice.ACTION_FOUND: {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
@ -119,7 +118,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, GBDevice.RSSI_UNKNOWN); short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, GBDevice.RSSI_UNKNOWN);
Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID); Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
ParcelUuid[] uuids2 = AndroidUtils.toParcelUUids(uuids); ParcelUuid[] uuids2 = AndroidUtils.toParcelUuids(uuids);
handleDeviceFound(device, rssi, uuids2); handleDeviceFound(device, rssi, uuids2);
break; break;
} }
@ -232,9 +231,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
public void logMessageContent(byte[] value) { public void logMessageContent(byte[] value) {
if (value != null) { if (value != null) {
for (byte b : value) { LOG.warn("DATA: " + GB.hexdump(value, 0, value.length));
LOG.warn("DATA: " + String.format("0x%2x", b) + " - " + (char) (b & 0xff));
}
} }
} }
@ -265,7 +262,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_discovery); setContentView(R.layout.activity_discovery);
startButton = (Button) findViewById(R.id.discovery_start); startButton = findViewById(R.id.discovery_start);
startButton.setOnClickListener(new View.OnClickListener() { startButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -273,11 +270,11 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
} }
}); });
progressView = (ProgressBar) findViewById(R.id.discovery_progressbar); progressView = findViewById(R.id.discovery_progressbar);
progressView.setProgress(0); progressView.setProgress(0);
progressView.setIndeterminate(true); progressView.setIndeterminate(true);
progressView.setVisibility(View.GONE); progressView.setVisibility(View.GONE);
ListView deviceCandidatesView = (ListView) findViewById(R.id.discovery_deviceCandidatesView); ListView deviceCandidatesView = findViewById(R.id.discovery_deviceCandidatesView);
cadidateListAdapter = new DeviceCandidateAdapter(this, deviceCandidates); cadidateListAdapter = new DeviceCandidateAdapter(this, deviceCandidates);
deviceCandidatesView.setAdapter(cadidateListAdapter); deviceCandidatesView.setAdapter(cadidateListAdapter);
@ -434,19 +431,33 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
} }
private void stopBTLEDiscovery() { private void stopBTLEDiscovery() {
if (adapter != null)
adapter.stopLeScan(leScanCallback); adapter.stopLeScan(leScanCallback);
} }
private void stopBTDiscovery() { private void stopBTDiscovery() {
if (adapter != null)
adapter.cancelDiscovery(); adapter.cancelDiscovery();
} }
@TargetApi(Build.VERSION_CODES.LOLLIPOP) @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void stopNewBTLEDiscovery() { private void stopNewBTLEDiscovery() {
adapter.getBluetoothLeScanner().stopScan(newLeScanCallback); if (adapter == null)
return;
BluetoothLeScanner bluetoothLeScanner = adapter.getBluetoothLeScanner();
if (bluetoothLeScanner == null) {
LOG.warn("could not get BluetoothLeScanner()!");
return;
}
if (newLeScanCallback == null) {
LOG.warn("newLeScanCallback == null!");
return;
}
bluetoothLeScanner.stopScan(newLeScanCallback);
} }
private void bluetoothStateChanged(int oldState, int newState) { private void bluetoothStateChanged(int newState) {
discoveryFinished(); discoveryFinished();
if (newState == BluetoothAdapter.STATE_ON) { if (newState == BluetoothAdapter.STATE_ON) {
this.adapter = BluetoothAdapter.getDefaultAdapter(); this.adapter = BluetoothAdapter.getDefaultAdapter();
@ -530,12 +541,12 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
private ScanSettings getScanSettings() { private ScanSettings getScanSettings() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return new ScanSettings.Builder() return new ScanSettings.Builder()
.setScanMode(SCAN_MODE_LOW_LATENCY) .setScanMode(android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_LATENCY)
.setMatchMode(MATCH_MODE_STICKY) .setMatchMode(android.bluetooth.le.ScanSettings.MATCH_MODE_STICKY)
.build(); .build();
} else { } else {
return new ScanSettings.Builder() return new ScanSettings.Builder()
.setScanMode(SCAN_MODE_LOW_LATENCY) .setScanMode(android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_LATENCY)
.build(); .build();
} }
} }
@ -611,4 +622,14 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
} }
} }
} }
@Override
protected void onPause() {
super.onPause();
stopBTDiscovery();
stopBTLEDiscovery();
if (GBApplication.isRunningLollipopOrLater()) {
stopNewBTLEDiscovery();
}
}
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo, Uwe Hermann Gobbetti, Lem Dulfo, Uwe Hermann
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -34,19 +34,26 @@ import android.widget.Toast;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.GBChromeClient; import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.GBChromeClient;
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.GBWebClient; import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.GBWebClient;
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.JSInterface; import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.JSInterface;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton; import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
public class ExternalPebbleJSActivity extends AbstractGBActivity { public class ExternalPebbleJSActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(ExternalPebbleJSActivity.class); private static final Logger LOG = LoggerFactory.getLogger(ExternalPebbleJSActivity.class);
@ -64,24 +71,69 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Bundle extras = getIntent().getExtras(); Bundle extras = getIntent().getExtras();
boolean showConfig = false;
UUID currentUUID = null;
GBDevice currentDevice = null;
if (extras == null) { if (extras == null) {
throw new IllegalArgumentException("Must provide device and uuid in extras when invoking this activity"); confUri = getIntent().getData();
if(confUri.getScheme().equals("gadgetbridge")) {
try {
currentUUID = UUID.fromString(confUri.getHost());
} catch (IllegalArgumentException e) {
LOG.error("UUID in incoming configuration is not a valid UUID: " +confUri.toString());
} }
//first check if we are still connected to a pebble
DeviceManager deviceManager = ((GBApplication) getApplication()).getDeviceManager();
List<GBDevice> deviceList = deviceManager.getDevices();
for (GBDevice device : deviceList) {
if (device.getState() == GBDevice.State.INITIALIZED) {
if (device.getType().equals(DeviceType.PEBBLE)) {
currentDevice = device;
break;
} else {
LOG.error("attempting to load pebble configuration but a different device type is connected!!!");
finish();
return;
}
}
}
if (currentDevice == null) {
//then try to reconnect to last connected device
String btDeviceAddress = GBApplication.getPrefs().getPreferences().getString("last_device_address", null);
if (btDeviceAddress != null) {
GBDevice candidate = DeviceHelper.getInstance().findAvailableDevice(btDeviceAddress, this);
if(!candidate.isConnected() && candidate.getType() == DeviceType.PEBBLE){
Intent intent = new Intent(this, DeviceCommunicationService.class)
.setAction(ACTION_CONNECT)
.putExtra(GBDevice.EXTRA_DEVICE, currentDevice);
this.startService(intent);
currentDevice = candidate;
}
}
}
showConfig = true; //we are getting incoming configuration data
}
} else {
currentDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
currentUUID = (UUID) extras.getSerializable(DeviceService.EXTRA_APP_UUID);
if (extras.getBoolean(START_BG_WEBVIEW, false)) { if (extras.getBoolean(START_BG_WEBVIEW, false)) {
startBackgroundWebViewAndFinish(); startBackgroundWebViewAndFinish();
return; return;
} }
showConfig = extras.getBoolean(SHOW_CONFIG, false);
GBDevice currentDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE); }
UUID currentUUID = (UUID) extras.getSerializable(DeviceService.EXTRA_APP_UUID);
if (GBApplication.getGBPrefs().isBackgroundJsEnabled()) { if (GBApplication.getGBPrefs().isBackgroundJsEnabled()) {
if (extras.getBoolean(SHOW_CONFIG, false)) { if (showConfig) {
Objects.requireNonNull(currentDevice, "Must provide a device when invoking this activity"); Objects.requireNonNull(currentDevice, "Must provide a device when invoking this activity");
Objects.requireNonNull(currentUUID, "Must provide a uuid when invoking this activity"); Objects.requireNonNull(currentUUID, "Must provide a uuid when invoking this activity");
WebViewSingleton.getInstance().runJavascriptInterface(this, currentDevice, currentUUID);
WebViewSingleton.runJavascriptInterface(currentDevice, currentUUID);
} }
// FIXME: is this really supposed to be outside the check for SHOW_CONFIG? // FIXME: is this really supposed to be outside the check for SHOW_CONFIG?
@ -104,7 +156,7 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity {
private void setupBGWebView() { private void setupBGWebView() {
setContentView(R.layout.activity_external_pebble_js); setContentView(R.layout.activity_external_pebble_js);
myWebView = WebViewSingleton.getWebView(this); myWebView = WebViewSingleton.getInstance().getWebView(this);
if (myWebView.getParent() != null) { if (myWebView.getParent() != null) {
((ViewGroup) myWebView.getParent()).removeView(myWebView); ((ViewGroup) myWebView.getParent()).removeView(myWebView);
} }
@ -161,6 +213,7 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity {
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
GB.toast("returned uri: " + confUri.toString(), Toast.LENGTH_LONG, GB.ERROR); GB.toast("returned uri: " + confUri.toString(), Toast.LENGTH_LONG, GB.ERROR);
} }
myWebView.stopLoading();
myWebView.loadUrl("file:///android_asset/app_config/configure.html?" + queryString); myWebView.loadUrl("file:///android_asset/app_config/configure.html?" + queryString);
} }
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Andreas Shimokawa /* Copyright (C) 2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo Gobbetti, Lem Dulfo
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -43,6 +43,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.ItemWithDetailsAdapter; import nodomain.freeyourgadget.gadgetbridge.adapter.ItemWithDetailsAdapter;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
@ -191,7 +192,7 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
} }
private InstallHandler findInstallHandlerFor(Uri uri) { private InstallHandler findInstallHandlerFor(Uri uri) {
for (DeviceCoordinator coordinator : DeviceHelper.getInstance().getAllCoordinators()) { for (DeviceCoordinator coordinator : getAllCoordinatorsConnectedFirst()) {
InstallHandler handler = coordinator.findInstallHandler(uri, this); InstallHandler handler = coordinator.findInstallHandler(uri, this);
if (handler != null) { if (handler != null) {
return handler; return handler;
@ -200,6 +201,29 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
return null; return null;
} }
private List<DeviceCoordinator> getAllCoordinatorsConnectedFirst() {
DeviceManager deviceManager = ((GBApplication) getApplicationContext()).getDeviceManager();
List<DeviceCoordinator> connectedCoordinators = new ArrayList<>();
List<DeviceCoordinator> allCoordinators = DeviceHelper.getInstance().getAllCoordinators();
List<DeviceCoordinator> sortedCoordinators = new ArrayList<>(allCoordinators.size());
GBDevice connectedDevice = deviceManager.getSelectedDevice();
if (connectedDevice != null && connectedDevice.isConnected()) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(connectedDevice);
if (coordinator != null) {
connectedCoordinators.add(coordinator);
}
}
sortedCoordinators.addAll(connectedCoordinators);
for (DeviceCoordinator coordinator : allCoordinators) {
if (!connectedCoordinators.contains(coordinator)) {
sortedCoordinators.add(coordinator);
}
}
return sortedCoordinators;
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Lem Dulfo
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Carsten Pfeiffer /* Copyright (C) 2016-2018 Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -27,4 +27,8 @@ public class HeartRateUtils {
* Value is in minutes * Value is in minutes
*/ */
public static final int MAX_HR_MEASUREMENTS_GAP_MINUTES = 10; public static final int MAX_HR_MEASUREMENTS_GAP_MINUTES = 10;
public static boolean isValidHeartRateValue(int value) {
return value > HeartRateUtils.MIN_HEART_RATE_VALUE && value < HeartRateUtils.MAX_HEART_RATE_VALUE;
}
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer, /* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti, Normano64 Daniele Gobbetti, Felix Konstantin Maurer, Normano64
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -18,7 +18,6 @@
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import android.Manifest; import android.Manifest;
import android.content.ContentUris;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@ -29,16 +28,13 @@ import android.location.Location;
import android.location.LocationListener; import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.preference.EditTextPreference; import android.preference.EditTextPreference;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceCategory; import android.preference.PreferenceCategory;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.provider.DocumentsContract; import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast; import android.widget.Toast;
@ -47,7 +43,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -65,6 +60,10 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs; 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.model.ActivityUser.PREF_USER_HEIGHT_CM; 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_SLEEP_DURATION;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_STEPS_GOAL; import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_STEPS_GOAL;
@ -330,6 +329,110 @@ public class SettingsActivity extends AbstractSettingsActivity {
} }
}); });
pref = findPreference("auto_fetch_interval_limit");
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object autoFetchInterval) {
String summary = String.format(
getApplicationContext().getString(R.string.pref_auto_fetch_limit_fetches_summary),
Integer.valueOf((String) autoFetchInterval));
preference.setSummary(summary);
return true;
}
});
int autoFetchInterval = GBApplication.getPrefs().getInt("auto_fetch_interval_limit", 0);
summary = String.format(
getApplicationContext().getString(R.string.pref_auto_fetch_limit_fetches_summary),
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;
}
});
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 // Get all receivers of Media Buttons
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
@ -377,7 +480,7 @@ public class SettingsActivity extends AbstractSettingsActivity {
} }
/* /*
Either returns the file path of the selected document, or the display name Either returns the file path of the selected document, or the display name, or an empty string
*/ */
private String getAutoExportLocationSummary() { private String getAutoExportLocationSummary() {
String autoExportLocation = GBApplication.getPrefs().getString(GBPrefs.AUTO_EXPORT_LOCATION, null); String autoExportLocation = GBApplication.getPrefs().getString(GBPrefs.AUTO_EXPORT_LOCATION, null);
@ -386,13 +489,9 @@ public class SettingsActivity extends AbstractSettingsActivity {
} }
Uri uri = Uri.parse(autoExportLocation); Uri uri = Uri.parse(autoExportLocation);
try { try {
String filePath = AndroidUtils.getFilePath(getApplicationContext(), uri); return AndroidUtils.getFilePath(getApplicationContext(), uri);
if (filePath != null) { } catch (IllegalArgumentException e) {
return filePath; try {
}
} catch (URISyntaxException e) {
return "";
}
Cursor cursor = getContentResolver().query( Cursor cursor = getContentResolver().query(
uri, uri,
new String[]{DocumentsContract.Document.COLUMN_DISPLAY_NAME}, new String[]{DocumentsContract.Document.COLUMN_DISPLAY_NAME},
@ -401,6 +500,11 @@ public class SettingsActivity extends AbstractSettingsActivity {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME)); return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
} }
}
catch (Exception fdfsdfds) {
LOG.warn("fuck");
}
}
return ""; return "";
} }
@ -457,6 +561,7 @@ public class SettingsActivity extends AbstractSettingsActivity {
PREF_USER_WEIGHT_KG, PREF_USER_WEIGHT_KG,
PREF_USER_SLEEP_DURATION, PREF_USER_SLEEP_DURATION,
PREF_USER_STEPS_GOAL, PREF_USER_STEPS_GOAL,
PREF_MI2_ENABLE_TEXT_NOTIFICATIONS,
"weather_city", "weather_city",
}; };
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo Gobbetti, Lem Dulfo
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -428,7 +428,7 @@ public abstract class AbstractAppManagerFragment extends Fragment {
startActivity(startIntent); startActivity(startIntent);
return true; return true;
case R.id.appmanager_app_openinstore: case R.id.appmanager_app_openinstore:
String url = "https://apps.getpebble.com/en_US/search/" + ((selectedApp.getType() == GBDeviceApp.Type.WATCHFACE) ? "watchfaces" : "watchapps") + "/1?query=" + selectedApp.getName() + "&dev_settings=true"; String url = "https://pebble-appstore.romanport.com/" + ((selectedApp.getType() == GBDeviceApp.Type.WATCHFACE) ? "watchfaces" : "watchapps") + "/0/?query=" + selectedApp.getName();
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url)); intent.setData(Uri.parse(url));
startActivity(intent); startActivity(intent);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa /* Copyright (C) 2016-2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Daniele Gobbetti /* Copyright (C) 2016-2018 Andreas Shimokawa, Daniele Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa /* Copyright (C) 2016-2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer, /* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti, walkjivefly Daniele Gobbetti, walkjivefly
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -70,6 +70,8 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import static nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils.isValidHeartRateValue;
/** /**
* A base class fragment to be used with ChartsActivity. The fragment can supply * A base class fragment to be used with ChartsActivity. The fragment can supply
* a title to be displayed in the activity by returning non-null in #getTitle() * a title to be displayed in the activity by returning non-null in #getTitle()
@ -439,6 +441,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
List<Entry> notWornEntries = new ArrayList<>(numEntries); List<Entry> notWornEntries = new ArrayList<>(numEntries);
boolean hr = supportsHeartrate(gbDevice); boolean hr = supportsHeartrate(gbDevice);
List<Entry> heartrateEntries = hr ? new ArrayList<Entry>(numEntries) : null; List<Entry> heartrateEntries = hr ? new ArrayList<Entry>(numEntries) : null;
List<Integer> colors = new ArrayList<>(numEntries); // this is kinda inefficient...
int lastHrSampleIndex = -1; int lastHrSampleIndex = -1;
for (int i = 0; i < numEntries; i++) { for (int i = 0; i < numEntries; i++) {
@ -509,7 +512,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
} }
activityEntries.add(createLineEntry(value, ts)); activityEntries.add(createLineEntry(value, ts));
} }
if (hr && isValidHeartRateValue(sample.getHeartRate())) { if (hr && sample.getKind() != ActivityKind.TYPE_NOT_WORN && HeartRateUtils.isValidHeartRateValue(sample.getHeartRate())) {
if (lastHrSampleIndex > -1 && ts - lastHrSampleIndex > 1800*HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) { if (lastHrSampleIndex > -1 && ts - lastHrSampleIndex > 1800*HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
heartrateEntries.add(createLineEntry(0, lastHrSampleIndex + 1)); heartrateEntries.add(createLineEntry(0, lastHrSampleIndex + 1));
heartrateEntries.add(createLineEntry(0, ts - 1)); heartrateEntries.add(createLineEntry(0, ts - 1));
@ -574,10 +577,6 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
return new DefaultChartsData(lineData, xValueFormatter); return new DefaultChartsData(lineData, xValueFormatter);
} }
protected boolean isValidHeartRateValue(int value) {
return value > HeartRateUtils.MIN_HEART_RATE_VALUE && value < HeartRateUtils.MAX_HEART_RATE_VALUE;
}
/** /**
* Implement this to supply the samples to be displayed. * Implement this to supply the samples to be displayed.
* *

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 0nse, Alberto, Andreas Shimokawa, Carsten Pfeiffer, /* Copyright (C) 2015-2018 0nse, Alberto, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti Daniele Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Vebryn Gobbetti, Vebryn
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -19,7 +19,6 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -85,16 +84,19 @@ class ActivityAnalysis {
} }
if (!stats.containsKey(steps)) { if (!stats.containsKey(steps)) {
LOG.info("Adding: " + steps); // LOG.debug("Adding: " + steps);
stats.put(steps, timeDifference); stats.put(steps, timeDifference);
} else { } else {
long time = stats.get(steps); long time = stats.get(steps);
LOG.info("Updating: " + steps + " " + timeDifference + time); // LOG.debug("Updating: " + steps + " " + timeDifference + time);
stats.put(steps, timeDifference + time); stats.put(steps, timeDifference + time);
} }
} }
} }
amount.setStartDate(sample.getTimestamp());
amount.setEndDate(sample.getTimestamp());
previousAmount = amount; previousAmount = amount;
previousSample = sample; previousSample = sample;
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -27,6 +27,7 @@ import android.view.ViewGroup;
import com.github.mikephil.charting.animation.Easing; import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.Chart; import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.LineChart; import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.LegendEntry; import com.github.mikephil.charting.components.LegendEntry;
import com.github.mikephil.charting.components.XAxis; import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis; import com.github.mikephil.charting.components.YAxis;
@ -177,6 +178,8 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
legendEntries.add(hrEntry); legendEntries.add(hrEntry);
} }
chart.getLegend().setCustom(legendEntries); chart.getLegend().setCustom(legendEntries);
chart.getLegend().setWordWrapEnabled(true);
chart.getLegend().setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER);
} }
@Override @Override

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Vebryn Gobbetti, Vebryn
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -36,15 +36,12 @@ import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
@ -52,6 +49,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.AbstractFragmentPagerAdap
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragmentActivity; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragmentActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
@ -59,16 +57,11 @@ import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
public class ChartsActivity extends AbstractGBFragmentActivity implements ChartsHost { public class ChartsActivity extends AbstractGBFragmentActivity implements ChartsHost {
private static final Logger LOG = LoggerFactory.getLogger(ChartsActivity.class);
private Button mPrevButton;
private Button mNextButton;
private TextView mDateControl; private TextView mDateControl;
private Date mStartDate; private Date mStartDate;
private Date mEndDate; private Date mEndDate;
private SwipeRefreshLayout swipeLayout; private SwipeRefreshLayout swipeLayout;
private NonSwipeableViewPager viewPager;
LimitedQueue mActivityAmountCache = new LimitedQueue(60); LimitedQueue mActivityAmountCache = new LimitedQueue(60);
@ -86,7 +79,7 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_charts_durationdialog); setContentView(R.layout.activity_charts_durationdialog);
durationLabel = (TextView) findViewById(R.id.charts_duration_label); durationLabel = findViewById(R.id.charts_duration_label);
setDuration(mDuration); setDuration(mDuration);
} }
@ -103,7 +96,7 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
switch (action) { switch (Objects.requireNonNull(action)) {
case GBDevice.ACTION_DEVICE_CHANGED: case GBDevice.ACTION_DEVICE_CHANGED:
GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE); GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
refreshBusyState(dev); refreshBusyState(dev);
@ -145,7 +138,7 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
throw new IllegalArgumentException("Must provide a device when invoking this activity"); throw new IllegalArgumentException("Must provide a device when invoking this activity");
} }
swipeLayout = (SwipeRefreshLayout) findViewById(R.id.activity_swipe_layout); swipeLayout = findViewById(R.id.activity_swipe_layout);
swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override @Override
public void onRefresh() { public void onRefresh() {
@ -155,7 +148,7 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
enableSwipeRefresh(true); enableSwipeRefresh(true);
// Set up the ViewPager with the sections adapter. // Set up the ViewPager with the sections adapter.
viewPager = (NonSwipeableViewPager) findViewById(R.id.charts_pager); NonSwipeableViewPager viewPager = findViewById(R.id.charts_pager);
viewPager.setAdapter(getPagerAdapter()); viewPager.setAdapter(getPagerAdapter());
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override @Override
@ -172,8 +165,8 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
} }
}); });
dateBar = (ViewGroup) findViewById(R.id.charts_date_bar); dateBar = findViewById(R.id.charts_date_bar);
mDateControl = (TextView) findViewById(R.id.charts_text_date); mDateControl = findViewById(R.id.charts_text_date);
mDateControl.setOnClickListener(new View.OnClickListener() { mDateControl.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -182,22 +175,20 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
} }
}); });
mPrevButton = (Button) findViewById(R.id.charts_previous); Button mPrevButton = findViewById(R.id.charts_previous);
mPrevButton.setOnClickListener(new View.OnClickListener() { mPrevButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
handlePrevButtonClicked(); handlePrevButtonClicked();
} }
}); });
mNextButton = (Button) findViewById(R.id.charts_next); Button mNextButton = findViewById(R.id.charts_next);
mNextButton.setOnClickListener(new View.OnClickListener() { mNextButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
handleNextButtonClicked(); handleNextButtonClicked();
} }
}); });
LinearLayout mainLayout = (LinearLayout) findViewById(R.id.charts_main_layout);
} }
private String formatDetailedDuration() { private String formatDetailedDuration() {
@ -284,7 +275,7 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
private void fetchActivityData() { private void fetchActivityData() {
if (getDevice().isInitialized()) { if (getDevice().isInitialized()) {
GBApplication.deviceService().onFetchActivityData(); GBApplication.deviceService().onFetchRecordedData(RecordedDataTypes.TYPE_ACTIVITY);
} else { } else {
swipeLayout.setRefreshing(false); swipeLayout.setRefreshing(false);
GB.toast(this, getString(R.string.device_not_connected), Toast.LENGTH_SHORT, GB.ERROR); GB.toast(this, getString(R.string.device_not_connected), Toast.LENGTH_SHORT, GB.ERROR);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Carsten Pfeiffer /* Copyright (C) 2016-2018 Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -168,7 +168,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
private void addSample(ActivitySample sample) { private void addSample(ActivitySample sample) {
int heartRate = sample.getHeartRate(); int heartRate = sample.getHeartRate();
int timestamp = tsTranslation.shorten(sample.getTimestamp()); int timestamp = tsTranslation.shorten(sample.getTimestamp());
if (isValidHeartRateValue(heartRate)) { if (HeartRateUtils.isValidHeartRateValue(heartRate)) {
setCurrentHeartRate(heartRate, timestamp); setCurrentHeartRate(heartRate, timestamp);
} }
int steps = sample.getSteps(); int steps = sample.getSteps();

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Carsten Pfeiffer /* Copyright (C) 2015-2018 Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer, /* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti Daniele Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -20,9 +20,11 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView;
import com.github.mikephil.charting.animation.Easing; import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.Chart; import com.github.mikephil.charting.charts.Chart;
@ -43,6 +45,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -62,6 +65,7 @@ public class SleepChartFragment extends AbstractChartFragment {
private LineChart mActivityChart; private LineChart mActivityChart;
private PieChart mSleepAmountChart; private PieChart mSleepAmountChart;
private TextView mSleepchartInfo;
private int mSmartAlarmFrom = -1; private int mSmartAlarmFrom = -1;
private int mSmartAlarmTo = -1; private int mSmartAlarmTo = -1;
@ -86,9 +90,25 @@ public class SleepChartFragment extends AbstractChartFragment {
List<Integer> colors = new ArrayList<>(); List<Integer> colors = new ArrayList<>();
// int index = 0; // int index = 0;
long totalSeconds = 0; long totalSeconds = 0;
Date startSleep = null;
Date endSleep = null;
for (ActivityAmount amount : amounts.getAmounts()) { for (ActivityAmount amount : amounts.getAmounts()) {
if ((amount.getActivityKind() & ActivityKind.TYPE_SLEEP) != 0) { if ((amount.getActivityKind() & ActivityKind.TYPE_SLEEP) != 0) {
long value = amount.getTotalSeconds(); 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; totalSeconds += value;
// entries.add(new PieEntry(value, index++)); // entries.add(new PieEntry(value, index++));
entries.add(new PieEntry(value, amount.getName(getActivity()))); entries.add(new PieEntry(value, amount.getName(getActivity())));
@ -112,7 +132,7 @@ public class SleepChartFragment extends AbstractChartFragment {
data.setDataSet(set); data.setDataSet(set);
//setupLegend(pieChart); //setupLegend(pieChart);
return new MySleepChartsData(totalSleep, data); return new MySleepChartsData(totalSleep, data, startSleep, endSleep);
} }
@Override @Override
@ -124,6 +144,15 @@ public class SleepChartFragment extends AbstractChartFragment {
mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317 mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
mActivityChart.getXAxis().setValueFormatter(mcd.getChartsData().getXValueFormatter()); mActivityChart.getXAxis().setValueFormatter(mcd.getChartsData().getXValueFormatter());
mActivityChart.setData(mcd.getChartsData().getData()); 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())));
} else {
mSleepchartInfo.setText(getContext().getString(R.string.you_did_not_sleep));
}
} }
@Override @Override
@ -136,8 +165,9 @@ public class SleepChartFragment extends AbstractChartFragment {
Bundle savedInstanceState) { Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_sleepchart, container, false); View rootView = inflater.inflate(R.layout.fragment_sleepchart, container, false);
mActivityChart = (LineChart) rootView.findViewById(R.id.sleepchart); mActivityChart = rootView.findViewById(R.id.sleepchart);
mSleepAmountChart = (PieChart) rootView.findViewById(R.id.sleepchart_pie_light_deep); mSleepAmountChart = rootView.findViewById(R.id.sleepchart_pie_light_deep);
mSleepchartInfo = rootView.findViewById(R.id.sleepchart_info);
setupActivityChart(); setupActivityChart();
setupSleepAmountChart(); setupSleepAmountChart();
@ -246,10 +276,14 @@ public class SleepChartFragment extends AbstractChartFragment {
private static class MySleepChartsData extends ChartsData { private static class MySleepChartsData extends ChartsData {
private String totalSleep; private String totalSleep;
private final PieData pieData; private final PieData pieData;
private @Nullable Date startSleep;
private @Nullable Date endSleep;
public MySleepChartsData(String totalSleep, PieData pieData) { public MySleepChartsData(String totalSleep, PieData pieData, @Nullable Date startSleep, @Nullable Date endSleep) {
this.totalSleep = totalSleep; this.totalSleep = totalSleep;
this.pieData = pieData; this.pieData = pieData;
this.startSleep = startSleep;
this.endSleep = endSleep;
} }
public PieData getPieData() { public PieData getPieData() {
@ -259,6 +293,16 @@ public class SleepChartFragment extends AbstractChartFragment {
public CharSequence getTotalSleep() { public CharSequence getTotalSleep() {
return totalSleep; return totalSleep;
} }
@Nullable
public Date getStartSleep() {
return startSleep;
}
@Nullable
public Date getEndSleep() {
return endSleep;
}
} }
private static class MyChartsData extends ChartsData { private static class MyChartsData extends ChartsData {

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer, /* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti, Vebryn Daniele Gobbetti, Vebryn
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Carsten Pfeiffer /* Copyright (C) 2016-2018 Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Carsten Pfeiffer /* Copyright (C) 2016-2018 Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2017-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -18,6 +18,7 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import com.github.mikephil.charting.charts.Chart; import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.components.AxisBase; 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.components.LegendEntry;
import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.formatter.IAxisValueFormatter; import com.github.mikephil.charting.formatter.IAxisValueFormatter;
@ -132,5 +133,7 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
chart.getLegend().setCustom(legendEntries); chart.getLegend().setCustom(legendEntries);
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR); chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
chart.getLegend().setWordWrapEnabled(true);
chart.getLegend().setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER);
} }
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer, /* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti Daniele Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -0,0 +1,122 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.content.Context;
import android.support.annotation.DrawableRes;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
/**
* Adapter for displaying generic ItemWithDetails instances.
*/
public abstract class AbstractItemAdapter<T> extends ArrayAdapter<T> {
public static final int SIZE_SMALL = 1;
public static final int SIZE_MEDIUM = 2;
public static final int SIZE_LARGE = 3;
private final Context context;
private final List<T> items;
private boolean horizontalAlignment;
private int size = SIZE_MEDIUM;
public AbstractItemAdapter(Context context) {
this (context, new ArrayList<T>());
}
public AbstractItemAdapter(Context context, List<T> items) {
super(context, 0, items);
this.context = context;
this.items = items;
}
public void setHorizontalAlignment(boolean horizontalAlignment) {
this.horizontalAlignment = horizontalAlignment;
}
@Override
public View getView(int position, View view, ViewGroup parent) {
T item = getItem(position);
if (view == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (horizontalAlignment) {
view = inflater.inflate(R.layout.item_with_details_horizontal, parent, false);
} else {
switch (size) {
case SIZE_SMALL:
view = inflater.inflate(R.layout.item_with_details_small, parent, false);
break;
default:
view = inflater.inflate(R.layout.item_with_details, parent, false);
break;
}
}
}
ImageView iconView = (ImageView) view.findViewById(R.id.item_image);
TextView nameView = (TextView) view.findViewById(R.id.item_name);
TextView detailsView = (TextView) view.findViewById(R.id.item_details);
nameView.setText(getName(item));
detailsView.setText(getDetails(item));
iconView.setImageResource(getIcon(item));
return view;
}
protected abstract String getName(T item);
protected abstract String getDetails(T item);
@DrawableRes
protected abstract int getIcon(T item);
public void setSize(int size) {
this.size = size;
}
public int getSize() {
return size;
}
public List<T> getItems() {
return items;
}
public void loadItems() {
}
public void setItems(List<T> items, boolean notify) {
this.items.clear();
this.items.addAll(items);
if (notify) {
notifyDataSetChanged();
}
}
}

View File

@ -0,0 +1,84 @@
/* Copyright (C) 2017-2018 Carsten Pfeiffer, Daniele Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.content.Context;
import android.widget.Toast;
import java.util.Date;
import java.util.List;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class ActivitySummariesAdapter extends AbstractItemAdapter<BaseActivitySummary> {
private final GBDevice device;
public ActivitySummariesAdapter(Context context, GBDevice device) {
super(context);
this.device = device;
loadItems();
}
@Override
public void loadItems() {
try (DBHandler handler = GBApplication.acquireDB()) {
BaseActivitySummaryDao summaryDao = handler.getDaoSession().getBaseActivitySummaryDao();
Device dbDevice = DBHelper.findDevice(device, handler.getDaoSession());
QueryBuilder<BaseActivitySummary> qb = summaryDao.queryBuilder();
qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(dbDevice.getId())).orderDesc(BaseActivitySummaryDao.Properties.StartTime);
List<BaseActivitySummary> allSummaries = qb.build().list();
setItems(allSummaries, true);
} catch (Exception e) {
GB.toast("Error loading activity summaries.", Toast.LENGTH_SHORT, GB.ERROR, e);
}
}
@Override
protected String getName(BaseActivitySummary item) {
String name = item.getName();
if (name != null && name.length() > 0) {
return name;
}
Date startTime = item.getStartTime();
if (startTime != null) {
return DateTimeUtils.formatDateTime(startTime);
}
return "Unknown activity";
}
@Override
protected String getDetails(BaseActivitySummary item) {
return ActivityKind.asString(item.getActivityKind(), getContext());
}
@Override
protected int getIcon(BaseActivitySummary item) {
return ActivityKind.getIconId(item.getActivityKind());
}
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Carsten Pfeiffer, Daniele Gobbetti /* Copyright (C) 2017-2018 Carsten Pfeiffer, Daniele Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -23,7 +23,7 @@ import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CheckBox; import android.widget.CheckedTextView;
import android.widget.Filter; import android.widget.Filter;
import android.widget.Filterable; import android.widget.Filterable;
import android.widget.ImageView; import android.widget.ImageView;
@ -32,12 +32,16 @@ import android.widget.TextView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import static nodomain.freeyourgadget.gadgetbridge.GBApplication.packageNameToPebbleMsgSender;
public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapter.AppBLViewHolder> implements Filterable { public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapter.AppBLViewHolder> implements Filterable {
private List<ApplicationInfo> applicationInfoList; private List<ApplicationInfo> applicationInfoList;
@ -62,7 +66,7 @@ public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapte
if (name == null) { if (name == null) {
name = ai.packageName; name = ai.packageName;
} }
if (GBApplication.appIsBlacklisted(ai.packageName)) { if (GBApplication.appIsNotifBlacklisted(ai.packageName) || GBApplication.appIsPebbleBlacklisted(packageNameToPebbleMsgSender(ai.packageName))) {
// sort blacklisted first by prefixing with a '!' // sort blacklisted first by prefixing with a '!'
name = "!" + name; name = "!" + name;
} }
@ -94,23 +98,52 @@ public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapte
holder.deviceAppNameLabel.setText(mNameMap.get(appInfo)); holder.deviceAppNameLabel.setText(mNameMap.get(appInfo));
holder.deviceImageView.setImageDrawable(appInfo.loadIcon(mPm)); holder.deviceImageView.setImageDrawable(appInfo.loadIcon(mPm));
holder.checkbox.setChecked(GBApplication.appIsBlacklisted(appInfo.packageName)); holder.blacklist_checkbox.setChecked(GBApplication.appIsNotifBlacklisted(appInfo.packageName));
holder.blacklist_pebble_checkbox.setChecked(GBApplication.appIsPebbleBlacklisted(packageNameToPebbleMsgSender(appInfo.packageName)));
holder.blacklist_pebble_checkbox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
((CheckedTextView) view).toggle();
if ( ((CheckedTextView)view).isChecked() ) {
GBApplication.addAppToPebbleBlacklist(appInfo.packageName);
} else {
GBApplication.removeFromAppsPebbleBlacklist(appInfo.packageName);
}
}
});
holder.itemView.setOnClickListener(new View.OnClickListener() { holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
CheckBox checkBox = ((CheckBox) v.findViewById(R.id.item_checkbox)); CheckedTextView checkBox = ((CheckedTextView) v.findViewById(R.id.item_checkbox));
checkBox.toggle(); checkBox.toggle();
if (checkBox.isChecked()) { if (checkBox.isChecked()) {
GBApplication.addAppToBlacklist(appInfo.packageName); GBApplication.addAppToNotifBlacklist(appInfo.packageName);
} else { } else {
GBApplication.removeFromAppsBlacklist(appInfo.packageName); GBApplication.removeFromAppsNotifBlacklist(appInfo.packageName);
} }
} }
}); });
} }
public void blacklistAllNotif() {
Set<String> apps_blacklist = new HashSet<>();
List<ApplicationInfo> allApps = mPm.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo ai : allApps) {
apps_blacklist.add(ai.packageName);
}
GBApplication.setAppsNotifBlackList(apps_blacklist);
notifyDataSetChanged();
}
public void whitelistAllNotif() {
Set<String> apps_blacklist = new HashSet<>();
GBApplication.setAppsNotifBlackList(apps_blacklist);
notifyDataSetChanged();
}
@Override @Override
public int getItemCount() { public int getItemCount() {
return applicationInfoList.size(); return applicationInfoList.size();
@ -123,9 +156,10 @@ public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapte
return applicationFilter; return applicationFilter;
} }
public class AppBLViewHolder extends RecyclerView.ViewHolder { class AppBLViewHolder extends RecyclerView.ViewHolder {
final CheckBox checkbox; final CheckedTextView blacklist_checkbox;
final CheckedTextView blacklist_pebble_checkbox;
final ImageView deviceImageView; final ImageView deviceImageView;
final TextView deviceAppVersionAuthorLabel; final TextView deviceAppVersionAuthorLabel;
final TextView deviceAppNameLabel; final TextView deviceAppNameLabel;
@ -133,7 +167,8 @@ public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapte
AppBLViewHolder(View itemView) { AppBLViewHolder(View itemView) {
super(itemView); super(itemView);
checkbox = (CheckBox) itemView.findViewById(R.id.item_checkbox); blacklist_checkbox = (CheckedTextView) itemView.findViewById(R.id.item_checkbox);
blacklist_pebble_checkbox = (CheckedTextView) itemView.findViewById(R.id.item_pebble_checkbox);
deviceImageView = (ImageView) itemView.findViewById(R.id.item_image); deviceImageView = (ImageView) itemView.findViewById(R.id.item_image);
deviceAppVersionAuthorLabel = (TextView) itemView.findViewById(R.id.item_details); deviceAppVersionAuthorLabel = (TextView) itemView.findViewById(R.id.item_details);
deviceAppNameLabel = (TextView) itemView.findViewById(R.id.item_name); deviceAppNameLabel = (TextView) itemView.findViewById(R.id.item_name);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo Gobbetti, Lem Dulfo
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -22,6 +22,7 @@ import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.widget.CardView; import android.support.v7.widget.CardView;
@ -43,14 +44,17 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms; import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity; import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9CalibrationActivity;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
@ -69,16 +73,16 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
this.deviceList = deviceList; this.deviceList = deviceList;
} }
@NonNull
@Override @Override
public GBDeviceAdapterv2.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public GBDeviceAdapterv2.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
this.parent = parent; this.parent = parent;
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.device_itemv2, parent, false); View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.device_itemv2, parent, false);
ViewHolder vh = new ViewHolder(view); return new ViewHolder(view);
return vh;
} }
@Override @Override
public void onBindViewHolder(ViewHolder holder, final int position) { public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
final GBDevice device = deviceList.get(position); final GBDevice device = deviceList.get(position);
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
@ -105,9 +109,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
return true; return true;
} }
}); });
holder.deviceImageView.setImageResource(R.drawable.level_list_device); holder.deviceImageView.setImageResource(device.isInitialized() ? device.getType().getIcon() : device.getType().getDisabledIcon());
//level-list does not allow negative values, hence we always add 100 to the key.
holder.deviceImageView.setImageLevel(device.getType().getKey() + 100 + (device.isInitialized() ? 100 : 0));
holder.deviceNameLabel.setText(getUniqueDeviceName(device)); holder.deviceNameLabel.setText(getUniqueDeviceName(device));
@ -143,7 +145,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
@Override @Override
public void onClick(View v) { public void onClick(View v) {
showTransientSnackbar(R.string.busy_task_fetch_activity_data); showTransientSnackbar(R.string.busy_task_fetch_activity_data);
GBApplication.deviceService().onFetchActivityData(); GBApplication.deviceService().onFetchRecordedData(RecordedDataTypes.TYPE_ACTIVITY);
} }
} }
); );
@ -210,6 +212,20 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
} }
); );
//show activity tracks
holder.showActivityTracks.setVisibility(coordinator.supportsActivityTracks() ? View.VISIBLE : View.GONE);
holder.showActivityTracks.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
Intent startIntent;
startIntent = new Intent(context, ActivitySummariesActivity.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent);
}
}
);
ItemWithDetailsAdapter infoAdapter = new ItemWithDetailsAdapter(context, device.getDeviceInfos()); ItemWithDetailsAdapter infoAdapter = new ItemWithDetailsAdapter(context, device.getDeviceInfos());
infoAdapter.setHorizontalAlignment(true); infoAdapter.setHorizontalAlignment(true);
holder.deviceInfoList.setAdapter(infoAdapter); holder.deviceInfoList.setAdapter(infoAdapter);
@ -232,7 +248,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
); );
holder.findDevice.setVisibility(device.isInitialized() ? View.VISIBLE : View.GONE); holder.findDevice.setVisibility(device.isInitialized() && coordinator.supportsFindDevice() ? View.VISIBLE : View.GONE);
holder.findDevice.setOnClickListener(new View.OnClickListener() holder.findDevice.setOnClickListener(new View.OnClickListener()
{ {
@ -275,6 +291,16 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
); );
holder.calibrateDevice.setVisibility(device.isInitialized() && device.getType() == DeviceType.WATCH9 ? View.VISIBLE : View.GONE);
holder.calibrateDevice.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent startIntent = new Intent(context, Watch9CalibrationActivity.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent);
}
});
//remove device, hidden under details //remove device, hidden under details
holder.removeDevice.setOnClickListener(new View.OnClickListener() holder.removeDevice.setOnClickListener(new View.OnClickListener()
@ -338,6 +364,8 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
ImageView manageAppsView; ImageView manageAppsView;
ImageView setAlarmsView; ImageView setAlarmsView;
ImageView showActivityGraphs; ImageView showActivityGraphs;
ImageView showActivityTracks;
ImageView calibrateDevice;
ImageView deviceInfoView; ImageView deviceInfoView;
//overflow //overflow
@ -348,44 +376,45 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
ViewHolder(View view) { ViewHolder(View view) {
super(view); super(view);
container = (CardView) view.findViewById(R.id.card_view); container = view.findViewById(R.id.card_view);
deviceImageView = (ImageView) view.findViewById(R.id.device_image); deviceImageView = view.findViewById(R.id.device_image);
deviceNameLabel = (TextView) view.findViewById(R.id.device_name); deviceNameLabel = view.findViewById(R.id.device_name);
deviceStatusLabel = (TextView) view.findViewById(R.id.device_status); deviceStatusLabel = view.findViewById(R.id.device_status);
//actions //actions
batteryStatusBox = (LinearLayout) view.findViewById(R.id.device_battery_status_box); batteryStatusBox = view.findViewById(R.id.device_battery_status_box);
batteryStatusLabel = (TextView) view.findViewById(R.id.battery_status); batteryStatusLabel = view.findViewById(R.id.battery_status);
batteryIcon = (ImageView) view.findViewById(R.id.device_battery_status); batteryIcon = view.findViewById(R.id.device_battery_status);
fetchActivityDataBox = (LinearLayout) view.findViewById(R.id.device_action_fetch_activity_box); fetchActivityDataBox = view.findViewById(R.id.device_action_fetch_activity_box);
fetchActivityData = (ImageView) view.findViewById(R.id.device_action_fetch_activity); fetchActivityData = view.findViewById(R.id.device_action_fetch_activity);
busyIndicator = (ProgressBar) view.findViewById(R.id.device_busy_indicator); busyIndicator = view.findViewById(R.id.device_busy_indicator);
takeScreenshotView = (ImageView) view.findViewById(R.id.device_action_take_screenshot); takeScreenshotView = view.findViewById(R.id.device_action_take_screenshot);
manageAppsView = (ImageView) view.findViewById(R.id.device_action_manage_apps); manageAppsView = view.findViewById(R.id.device_action_manage_apps);
setAlarmsView = (ImageView) view.findViewById(R.id.device_action_set_alarms); setAlarmsView = view.findViewById(R.id.device_action_set_alarms);
showActivityGraphs = (ImageView) view.findViewById(R.id.device_action_show_activity_graphs); showActivityGraphs = view.findViewById(R.id.device_action_show_activity_graphs);
deviceInfoView = (ImageView) view.findViewById(R.id.device_info_image); showActivityTracks = view.findViewById(R.id.device_action_show_activity_tracks);
deviceInfoView = view.findViewById(R.id.device_info_image);
calibrateDevice = view.findViewById(R.id.device_action_calibrate);
deviceInfoBox = (RelativeLayout) view.findViewById(R.id.device_item_infos_box); deviceInfoBox = view.findViewById(R.id.device_item_infos_box);
//overflow //overflow
deviceInfoList = (ListView) view.findViewById(R.id.device_item_infos); deviceInfoList = view.findViewById(R.id.device_item_infos);
findDevice = (ImageView) view.findViewById(R.id.device_action_find); findDevice = view.findViewById(R.id.device_action_find);
removeDevice = (ImageView) view.findViewById(R.id.device_action_remove); removeDevice = view.findViewById(R.id.device_action_remove);
} }
} }
public void justifyListViewHeightBasedOnChildren(ListView listView) { private void justifyListViewHeightBasedOnChildren(ListView listView) {
ArrayAdapter adapter = (ArrayAdapter) listView.getAdapter(); ArrayAdapter adapter = (ArrayAdapter) listView.getAdapter();
if (adapter == null) { if (adapter == null) {
return; return;
} }
ViewGroup vg = listView;
int totalHeight = 0; int totalHeight = 0;
for (int i = 0; i < adapter.getCount(); i++) { for (int i = 0; i < adapter.getCount(); i++) {
View listItem = adapter.getView(i, null, vg); View listItem = adapter.getView(i, null, listView);
listItem.measure(0, 0); listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight(); totalHeight += listItem.getMeasuredHeight();
} }
@ -427,11 +456,11 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
private void showTransientSnackbar(int resource) { private void showTransientSnackbar(int resource) {
Snackbar snackbar = Snackbar.make(parent, resource, Snackbar.LENGTH_SHORT); Snackbar snackbar = Snackbar.make(parent, resource, Snackbar.LENGTH_SHORT);
View snackbarView = snackbar.getView(); //View snackbarView = snackbar.getView();
// change snackbar text color // change snackbar text color
int snackbarTextId = android.support.design.R.id.snackbar_text; //int snackbarTextId = android.support.design.R.id.snackbar_text;
TextView textView = (TextView) snackbarView.findViewById(snackbarTextId); //TextView textView = snackbarView.findViewById(snackbarTextId);
//textView.setTextColor(); //textView.setTextColor();
//snackbarView.setBackgroundColor(Color.MAGENTA); //snackbarView.setBackgroundColor(Color.MAGENTA);
snackbar.show(); snackbar.show();

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -17,77 +17,33 @@
package nodomain.freeyourgadget.gadgetbridge.adapter; package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.content.Context; import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List; import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails; import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
/** /**
* Adapter for displaying generic ItemWithDetails instances. * Adapter for displaying generic ItemWithDetails instances.
*/ */
public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> { public class ItemWithDetailsAdapter extends AbstractItemAdapter<ItemWithDetails> {
public static final int SIZE_SMALL = 1;
public static final int SIZE_MEDIUM = 2;
public static final int SIZE_LARGE = 3;
private final Context context;
private boolean horizontalAlignment;
private int size = SIZE_MEDIUM;
public ItemWithDetailsAdapter(Context context, List<ItemWithDetails> items) { public ItemWithDetailsAdapter(Context context, List<ItemWithDetails> items) {
super(context, 0, items); super(context, items);
this.context = context;
}
public void setHorizontalAlignment(boolean horizontalAlignment) {
this.horizontalAlignment = horizontalAlignment;
} }
@Override @Override
public View getView(int position, View view, ViewGroup parent) { protected String getName(ItemWithDetails item) {
ItemWithDetails item = getItem(position); return item.getName();
if (view == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (horizontalAlignment) {
view = inflater.inflate(R.layout.item_with_details_horizontal, parent, false);
} else {
switch (size) {
case SIZE_SMALL:
view = inflater.inflate(R.layout.item_with_details_small, parent, false);
break;
default:
view = inflater.inflate(R.layout.item_with_details, parent, false);
break;
}
}
}
ImageView iconView = (ImageView) view.findViewById(R.id.item_image);
TextView nameView = (TextView) view.findViewById(R.id.item_name);
TextView detailsView = (TextView) view.findViewById(R.id.item_details);
nameView.setText(item.getName());
detailsView.setText(item.getDetails());
iconView.setImageResource(item.getIcon());
return view;
} }
public void setSize(int size) { @Override
this.size = size; protected String getDetails(ItemWithDetails item) {
return item.getDetails();
} }
public int getSize() { @Override
return size; protected int getIcon(ItemWithDetails item) {
return item.getIcon();
} }
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, JohnnySun Gobbetti, JohnnySun
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, JohnnySun Gobbetti, Felix Konstantin Maurer, JohnnySun
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2018 Carsten Pfeiffer, Felix Konstantin Maurer
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.database; package nodomain.freeyourgadget.gadgetbridge.database;
import android.app.AlarmManager; import android.app.AlarmManager;
@ -65,8 +81,9 @@ public class PeriodicExporter extends BroadcastReceiver {
return; return;
} }
Uri dstUri = Uri.parse(dst); Uri dstUri = Uri.parse(dst);
OutputStream out = context.getContentResolver().openOutputStream(dstUri); try (OutputStream out = context.getContentResolver().openOutputStream(dstUri)) {
helper.exportDB(dbHandler, out); helper.exportDB(dbHandler, out);
}
} catch (Exception ex) { } catch (Exception ex) {
GB.updateExportFailedNotification(context.getString(R.string.notif_export_failed_title), context); GB.updateExportFailedNotification(context.getString(R.string.notif_export_failed_title), context);
LOG.info("Exception while exporting DB: ", ex); LOG.info("Exception while exporting DB: ", ex);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa /* Copyright (C) 2016-2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa /* Copyright (C) 2016-2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017 protomors /* Copyright (C) 2017-2018 protomors
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Daniele Gobbetti /* Copyright (C) 2015-2018 Andreas Shimokawa, Daniele Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -18,6 +18,9 @@ package nodomain.freeyourgadget.gadgetbridge.deviceevents;
public abstract class GBDeviceEvent { public abstract class GBDeviceEvent {
@Override
public String toString() {
return getClass().getSimpleName() + ": ";
}
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa /* Copyright (C) 2015-2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Daniele Gobbetti /* Copyright (C) 2015-2018 Andreas Shimokawa, Daniele Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa /* Copyright (C) 2015-2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa /* Copyright (C) 2015-2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa /* Copyright (C) 2015-2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa /* Copyright (C) 2015-2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa /* Copyright (C) 2015-2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa /* Copyright (C) 2015-2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa /* Copyright (C) 2015-2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -22,4 +22,9 @@ import nodomain.freeyourgadget.gadgetbridge.R;
public class GBDeviceEventVersionInfo extends GBDeviceEvent { public class GBDeviceEventVersionInfo extends GBDeviceEvent {
public String fwVersion = GBApplication.getContext().getString(R.string.n_a); public String fwVersion = GBApplication.getContext().getString(R.string.n_a);
public String hwVersion = GBApplication.getContext().getString(R.string.n_a); public String hwVersion = GBApplication.getContext().getString(R.string.n_a);
@Override
public String toString() {
return super.toString() + "fwVersion: " + fwVersion + "; hwVersion: " + hwVersion;
}
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Andreas Shimokawa /* Copyright (C) 2017-2018 Andreas Shimokawa
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Carsten Pfeiffer, Daniele Gobbetti /* Copyright (C) 2015-2018 Carsten Pfeiffer, Daniele Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -124,4 +124,14 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
public int getBondingStyle(GBDevice device) { public int getBondingStyle(GBDevice device) {
return BONDING_STYLE_ASK; return BONDING_STYLE_ASK;
} }
@Override
public boolean supportsActivityTracks() {
return false;
}
@Override
public boolean supportsMusicInfo() {
return false;
}
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, JohnnySun, Uwe Hermann Gobbetti, JohnnySun, Uwe Hermann
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -139,6 +139,14 @@ public interface DeviceCoordinator {
*/ */
boolean supportsActivityTracking(); boolean supportsActivityTracking();
/**
* Indicates whether the device supports recording dedicated activity tracks, like
* walking, hiking, running, swimming, etc. and retrieving the recorded
* data. This is different from the constant activity tracking since the tracks are
* usually recorded with additional features, like e.g. GPS.
*/
boolean supportsActivityTracks();
/** /**
* Returns true if activity data fetching is supported AND possible at this * Returns true if activity data fetching is supported AND possible at this
* very moment. This will consider the device state (being connected/disconnected/busy...) * very moment. This will consider the device state (being connected/disconnected/busy...)
@ -234,4 +242,16 @@ public interface DeviceCoordinator {
* forecast display. * forecast display.
*/ */
boolean supportsWeather(); boolean supportsWeather();
/**
* Indicates whether the device supports being found by vibrating,
* making some sound or lighting up
*/
boolean supportsFindDevice();
/**
* Indicates whether the device supports displaying music information
* like artist, title, album, play state etc.
*/
boolean supportsMusicInfo();
} }

View File

@ -1,4 +1,5 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer /* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -39,6 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
/** /**
* Provides access to the list of devices managed by Gadgetbridge. * Provides access to the list of devices managed by Gadgetbridge.
@ -149,6 +151,8 @@ public class DeviceManager {
} }
} }
} }
GB.updateNotification(selectedDevice, context);
} }
private void refreshPairedDevices() { private void refreshPairedDevices() {
@ -163,8 +167,11 @@ public class DeviceManager {
Collections.sort(deviceList, new Comparator<GBDevice>() { Collections.sort(deviceList, new Comparator<GBDevice>() {
@Override @Override
public int compare(GBDevice lhs, GBDevice rhs) { public int compare(GBDevice lhs, GBDevice rhs) {
if (rhs.getStateOrdinal() - lhs.getStateOrdinal() == 0) {
return Collator.getInstance().compare(lhs.getName(), rhs.getName()); return Collator.getInstance().compare(lhs.getName(), rhs.getName());
} }
return (rhs.getStateOrdinal() - lhs.getStateOrdinal());
}
}); });
notifyDevicesChanged(); notifyDevicesChanged();
} }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Julien Pivotto, Kasha, Steffen Liebergeld, Uwe Hermann Gobbetti, Julien Pivotto, Kasha, Steffen Liebergeld, Uwe Hermann
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -67,7 +67,7 @@ public interface EventHandler {
void onAppReorder(UUID uuids[]); void onAppReorder(UUID uuids[]);
void onFetchActivityData(); void onFetchRecordedData(int dataTypes);
void onReboot(); void onReboot();

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