1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-27 12:26:48 +01:00

Merge branch 'master' into zetime

This commit is contained in:
Sebastian Kranz 2018-06-29 10:43:11 +02:00
commit 1b152c86ea
514 changed files with 11625 additions and 1986 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,77 @@
### Changelog ### Changelog
#### 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
* Amazfit Bip: Fix language setting on new firmwares
#### Version 0.24.3 #### Version 0.24.3
* Charts: Try to fix another crash * Charts: Try to fix another crash
* Pebble: Fix weather for some watchfaces when using background JS * Pebble: Fix weather for some watchfaces when using background JS
@ -52,7 +124,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
@ -74,7 +146,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
@ -121,7 +193,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
@ -140,7 +212,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
@ -211,7 +283,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
@ -253,9 +325,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
@ -275,7 +347,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
@ -352,7 +424,7 @@
#### Version 0.13.7 #### Version 0.13.7
* Pebble: Fix configuration of certain pebble apps (eg. QR Generator, Squared 4.0) * Pebble: Fix configuration of certain pebble apps (eg. QR Generator, Squared 4.0)
* Pebble: Add context menu option in app manager to search a watchapp in the pebble appstore * Pebble: Add context menu option in app manager to search a watchapp in the pebble app store
* Mi Band: allow to delete Mi Band address from development settings * Mi Band: allow to delete Mi Band address from development settings
* Mi Band 2: Initial support for heart rate readings (Debug activity only) * Mi Band 2: Initial support for heart rate readings (Debug activity only)
* Mi Band 2: Support disabled alarms * Mi Band 2: Support disabled alarms
@ -523,7 +595,7 @@
* Fix enabling log file writing #261 * Fix enabling log file writing #261
#### Version 0.9.0 #### Version 0.9.0
* Pebble: Support for configuring watchfaces/apps locally (clay) or though webbrowser (some do not work) * Pebble: Support for configuring watchfaces/apps locally (clay) or though web browser (some do not work)
* Pebble: hide the alarm management activity as it's unsupported * Pebble: hide the alarm management activity as it's unsupported
* Mi Band: Improve firmware detection and updates, including 1S support * Mi Band: Improve firmware detection and updates, including 1S support
* Mi Band: Display HR FW for 1S * Mi Band: Display HR FW for 1S
@ -671,7 +743,7 @@
* Pebble: Allow to treat K9 notifications as generic notifications (if notification mode is set to never) * Pebble: Allow to treat K9 notifications as generic notifications (if notification mode is set to never)
* Ignore QKSMS notifications to avoid double notification for incoming SMS * Ignore QKSMS notifications to avoid double notification for incoming SMS
* Improved UI of Firmware/App installer * Improved UI of Firmware/App installer
* Device state again visible on lockscreen * Device state again visible on lock screen
* Date display and navigation now working properly for all charts * Date display and navigation now working properly for all charts
#### Version 0.5.2 #### Version 0.5.2
@ -714,7 +786,7 @@
* Fixed crash when synchronizing activity data in the graphs activity and changing device orientation * Fixed crash when synchronizing activity data in the graphs activity and changing device orientation
#### Version 0.4.4 #### Version 0.4.4
* Set Gadgetbridge notification visibility to public, to show the connection status on the lockscreen * Set Gadgetbridge notification visibility to public, to show the connection status on the lock screen
* Support for backup up and restoring of the activity database (via Debug activity) * Support for backup up and restoring of the activity database (via Debug activity)
* Support for graceful upgrades and downgrades, keeping your activity database intact * Support for graceful upgrades and downgrades, keeping your activity database intact
* Enhancement to activity graphs: new graphs for sleep data (only last night) accessible swiping right from the main graph * Enhancement to activity graphs: new graphs for sleep data (only last night) accessible swiping right from the main graph
@ -804,7 +876,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,10 +68,13 @@ 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); addZeTimeActivitySample(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");
} }
@ -270,6 +276,17 @@ 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) { private static Entity addZeTimeActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "ZeTimeActivitySample"); Entity activitySample = addEntity(schema, "ZeTimeActivitySample");
activitySample.implementsSerializable(); activitySample.implementsSerializable();
@ -309,6 +326,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

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

View File

@ -9,7 +9,7 @@ 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)
@ -33,6 +33,7 @@ vendor's servers.
* NO.1 F1 (WIP) * NO.1 F1 (WIP)
* Liveview * Liveview
* Vibratissimo (experimental) * Vibratissimo (experimental)
* XWatch (Affordable Chinese Casio-like smartwatches)
## Features ## Features
@ -94,7 +95,7 @@ For more information read [this wiki article](https://github.com/Freeyourgadget/
## Contribute ## Contribute
Contributions are welcome, be it feedback, bugreports, documentation, translation, research or code. Feel free to work Contributions are welcome, be it feedback, bug reports, documentation, translation, research or code. Feel free to work
on any of the open [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues?q=is%3Aopen+is%3Aissue); on any of the open [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues?q=is%3Aopen+is%3Aissue);
just leave a comment that you're working on one to avoid duplicated work. just leave a comment that you're working on one to avoid duplicated work.

View File

@ -1,14 +1,13 @@
apply plugin: "com.android.application"
apply plugin: "findbugs"
apply plugin: "pmd"
apply plugin: 'com.android.application' def ABORT_ON_CHECK_FAILURE = false
apply plugin: 'findbugs'
apply plugin: 'pmd'
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.3" versionName "0.27.0"
versionCode 120 versionCode 132
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,7 +38,8 @@
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
@ -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"
@ -414,7 +420,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,58 +373,119 @@ 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;
public static boolean calendarIsBlacklisted(String calendarDisplayName) { public static boolean calendarIsBlacklisted(String calendarDisplayName) {
@ -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.

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,11 +77,9 @@ 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;
}
} }
} }
}; };
@ -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,7 +273,9 @@ public class DebugActivity extends AbstractGBActivity {
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.extend(wearableExtender); .extend(wearableExtender);
nManager.notify((int) System.currentTimeMillis(), ncomp.build()); if (nManager != null) {
nManager.notify((int) System.currentTimeMillis(), ncomp.build());
}
} }
@Override @Override
@ -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;
} }
@ -265,7 +264,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 +272,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);
@ -443,10 +442,15 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
@TargetApi(Build.VERSION_CODES.LOLLIPOP) @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void stopNewBTLEDiscovery() { private void stopNewBTLEDiscovery() {
adapter.getBluetoothLeScanner().stopScan(newLeScanCallback); BluetoothLeScanner bluetoothLeScanner = adapter.getBluetoothLeScanner();
if (bluetoothLeScanner == null) {
LOG.warn("could not get BluetoothLeScanner()!");
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 +534,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 +615,14 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
} }
} }
} }
@Override
protected void onPause() {
super.onPause();
stopBTDiscovery();
stopBTLEDiscovery();
if (GB.supportsBluetoothLE()) {
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());
}
if (extras.getBoolean(START_BG_WEBVIEW, false)) { //first check if we are still connected to a pebble
startBackgroundWebViewAndFinish(); DeviceManager deviceManager = ((GBApplication) getApplication()).getDeviceManager();
return; 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;
}
}
}
GBDevice currentDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE); showConfig = true; //we are getting incoming configuration data
UUID currentUUID = (UUID) extras.getSerializable(DeviceService.EXTRA_APP_UUID); }
} else {
currentDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
currentUUID = (UUID) extras.getSerializable(DeviceService.EXTRA_APP_UUID);
if (extras.getBoolean(START_BG_WEBVIEW, false)) {
startBackgroundWebViewAndFinish();
return;
}
showConfig = extras.getBoolean(SHOW_CONFIG, false);
}
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.

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,7 @@ 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_DISPLAY_ITEMS;
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 +326,20 @@ public class SettingsActivity extends AbstractSettingsActivity {
} }
}); });
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;
}
});
// 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 +387,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,20 +396,21 @@ 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 {
Cursor cursor = getContentResolver().query(
uri,
new String[]{DocumentsContract.Document.COLUMN_DISPLAY_NAME},
null, null, null, null
);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
}
}
catch (Exception fdfsdfds) {
LOG.warn("fuck");
} }
} catch (URISyntaxException e) {
return "";
}
Cursor cursor = getContentResolver().query(
uri,
new String[] { DocumentsContract.Document.COLUMN_DISPLAY_NAME },
null, null, null, null
);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
} }
return ""; return "";
} }

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.

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, 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.

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.

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,6 +44,7 @@ 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;
@ -51,6 +53,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
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 +72,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);
@ -143,7 +146,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 +213,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);
@ -338,6 +355,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
ImageView manageAppsView; ImageView manageAppsView;
ImageView setAlarmsView; ImageView setAlarmsView;
ImageView showActivityGraphs; ImageView showActivityGraphs;
ImageView showActivityTracks;
ImageView deviceInfoView; ImageView deviceInfoView;
//overflow //overflow
@ -348,44 +366,44 @@ 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);
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 +445,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.

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.

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

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...)

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,7 +167,10 @@ 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) {
return Collator.getInstance().compare(lhs.getName(), rhs.getName()); if (rhs.getStateOrdinal() - lhs.getStateOrdinal() == 0) {
return Collator.getInstance().compare(lhs.getName(), rhs.getName());
}
return (rhs.getStateOrdinal() - lhs.getStateOrdinal());
} }
}); });
notifyDevicesChanged(); notifyDevicesChanged();

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