Merge branch 'master' into feature-weather

master
Andreas Shimokawa 2016-05-16 14:15:39 +02:00
commit dafdb1008d
218 changed files with 7255 additions and 1445 deletions

2
.gitignore vendored
View File

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

View File

@ -1,4 +1,12 @@
language: android language: android
jdk:
- oraclejdk8
- oraclejdk7
env:
- GRADLE_OPTS="-XX:MaxPermSize=256m"
android: android:
components: components:
# Uncomment the lines below if you want to # Uncomment the lines below if you want to
@ -7,7 +15,7 @@ android:
- tools - tools
# The BuildTools version used by your project # The BuildTools version used by your project
- build-tools-23.0.2 - build-tools-23.0.3
# The SDK version used to compile your project # The SDK version used to compile your project
- android-23 - android-23

View File

@ -1,4 +1,92 @@
###Changelog ###Changelog
####Version 0.9.7
* Pebble: hopefully fix some reconnect issues
* Mi Band: fix live activity monitoring running forever if back button pressed
* Mi Band: allow low latency firmware updates, fixes update with some phones
* Mi Band: inital experimental and probably broken support for Amazfit
* Show aliases for BT Devices if they had been renamed in BT Settings
* Do not show a hint about App Manager when a Mi Band is connected
####Version 0.9.6
* Again some UI/theme improvements
* New preference to reconnect after connection loss (defaults to true)
* Fix crash when dealing with certain old preference values
* Mi Band: automatically reconnect when back in range after connection loss
* Mi Band 1S: display heart rate value again when invoked via the Debug view
####Version 0.9.5
* Several UI Improvements
* Easier First-time setup by using a FAB
* Optional Dark Theme
* Notification App Blacklist is now sorted
* Gadgetbridge Icon in the notification bar displays connection state
* Logging is now configurable without restart
* Mi Band 1S: Initial live heartrate tracking
* Fix certain crash in charts activity on slower devices (#277)
####Version 0.9.4
* Pebble: support pebble health datalog messages of firmware 3.11 (this adds support for deep sleep!)
* Pebble: try to reconnect on new notifications and phone calls when connection was lost unexpectedly
* Pebble: delay between reconnection attempts (from 1 up to 64 seconds)
* Fix crash in charts activities when changing the date, quickly (#277)
* Mi Band: preference to enable heart rate measurement during sleep (#232, thanks computerlyrik!)
* Mi Band: display measured heart rate in charts (#232)
* Mi Band 1S: full support for firmware upgrade/downgrade (both for Mi Band and heart rate sensor) (#234)
* Mi Band 1S: fix device detection for certain versions
####Version 0.9.3
* Pebble: Fix Pebble Health activation (was not available in the App Manager)
* Simplify connection state display (only connecting->connected)
* Small improvements to the pairing activity
* Mi Band 1S: Fix for mi band firmware update
####Version 0.9.2
* Mi Band: Fix update of second (HR) firmware on Mi1S (#234)
* Fix ordering issue of device infos being displayed
####Version 0.9.1
* Mi Band: fix sporadic connection problems (stuck on "Initializing" #249)
* Mi Band: enable low latency connection (faster) during initialization and activity sync
* Mi Band: better feedback for firmware update
* Device Item is now clickable also when the information entries are visible
* Fix enabling log file writing #261
####Version 0.9.0
* Pebble: Support for configuring watchfaces/apps locally (clay) or though webbrowser (some do not work)
* Pebble: hide the alarm management activity as it's unsupported
* Mi Band: Improve firmware detection and updates, including 1S support
* Mi Band: Display HR FW for 1S
* FW and HW versions are only displayed after tapping on the "info" button in Control Center
* Do not display activity samples when navigating too far in the past
* Fix auto connect which was broken under some circumstances
####Version 0.8.2
* Fix database creation and updates (thanks @feclare)
* Add experimental widget to set the alarm time to a configurable number of hours in the future (thanks @0nse)
* Use ckChangeLog to display the Changelog within Gadgetbridge
* Workaround to fix logfile rotation (bug in logback-android)
####Version 0.8.1
* Pebble: install (and start) freshly-installed apps on the watch instead of showing a Toast that tells the user to do so. (only applies to firmware 3.x)
* Pebble: fix crash while receiving Health data
* Mi Band 1S: support for synchronizing activity data (#205)
* Mi Band 1S: support for reading the heart rate via the "Debug Screen" #178
####Version 0.8.0
* Pebble: Support Pebble Health: steps/activity data are stored correctly. Sleep time is considered as light sleep. Deep sleep is discarded. The pebble will send data where it seems appropriate, there is no action to perform on the watch for this to happen.
* Pebble: Fix support for newer version of morpheuz (>=3.3?)
* Pebble: Allow to select the preferred activity tracker via settings activity (Health, Misfit, Morpheuz)
* Pebble: Fix wrong(previous) contact being displayed on the pebble
* Mi Band: improvements to pairing and connecting
* Fix a problem related to shared preferences storage of activity settings
* Very basic support Android 6 runtime permission
* Fix layout of the alarms activity
####Version 0.7.4
* Refactored the settings activity: User details are now generic instead of miband specific. Old settings are preserved.
* Pebble: Fix regression with broken active reconnect since 0.7.0
* Pebble: Support activation and deactivation of Pebble Health. Activation uses the User details as seen above. Insigths are NOT activated.
Please be aware that deactivation does NOT delete the data stored on the watch (but it seems to stop the tracking), and we do not know how to switch to metric length units.
####Version 0.7.3 ####Version 0.7.3
* Pebble: Report connection state to PebbleKit companion apps via content provider. NOTE: Makes Gadgetbridge mutual exclusive with the original Pebble app. * Pebble: Report connection state to PebbleKit companion apps via content provider. NOTE: Makes Gadgetbridge mutual exclusive with the original Pebble app.

View File

@ -13,6 +13,10 @@ need to create an account and transmit any of your data to the vendor's servers.
[List of changes](CHANGELOG.md) [List of changes](CHANGELOG.md)
## Supported Devices
* Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, Pebble Time Round
* Mi Band, Mi Band 1A, Mi Band 1S (experimental)
## Features (Pebble) ## Features (Pebble)
* Incoming calls notification and display * Incoming calls notification and display
@ -31,20 +35,22 @@ need to create an account and transmit any of your data to the vendor's servers.
* Install firwmare files (.pbz) [READ THE WIKI](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble-Firmware-updates) * Install firwmare files (.pbz) [READ THE WIKI](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble-Firmware-updates)
* Install language files (.pbl) * Install language files (.pbl)
* Take and share screenshots from the Pebble's screen * Take and share screenshots from the Pebble's screen
* PebbleKit support for 3rd Party Android Apps support (experimental) * PebbleKit support for 3rd Party Android Apps (experimental)
* Morpheuz sleep data syncronization (experimental) * Fetch activity data from Pebble Health, Misfit and Morpheuz (experimental)
* Misfit steps data synchronization (experimental) * Configure watchfaces / apps (limited compatibility, experimental)
## Notes about Firmware 3.x (Pebble Time, updated OG) ## Notes about Firmware 3.x (Pebble Time, updated OG)
* Listing installed watchfaces will simply display previously installed watchapps, no matter if they are still installed or not. * Listing installed watchfaces will simply display previously installed watchapps, no matter if they are still installed or not.
## How to use (Pebble) ## Getting Started (Pebble)
1. Pair your Pebble through Gadgetbridge's Discovery Activity or the Android Bluetooth Settings 1. Pair your Pebble through the Android's Bluetooth Settings
2. Start Gadgetbridge, tap on the device you want to connect to 2. Start Gadgetbridge, tap on the device you want to connect to
3. To test, choose "Debug" from the menu and play around 3. To test, choose "Debug" from the menu and play around
For more information read [this wiki article](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Getting-Started-(Pebble))
## Features (Mi Band) ## Features (Mi Band)
* Mi Band notifications (LEDs + vibration) for * Mi Band notifications (LEDs + vibration) for
@ -55,6 +61,8 @@ need to create an account and transmit any of your data to the vendor's servers.
* Generic Android notifications * Generic Android notifications
* Synchronize the time to the Mi Band * Synchronize the time to the Mi Band
* Display firmware version and battery state * Display firmware version and battery state
* Firmware Update
* Heartrate Measurement (alpha)
* Synchronize activity data * Synchronize activity data
* Display sleep data (alpha) * Display sleep data (alpha)
* Display sports data (step count) (alpha) * Display sports data (step count) (alpha)
@ -95,9 +103,12 @@ Contributions are welcome, be it feedback, bugreports, documentation, translatio
on any of the open [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues?q=is%3Aopen+is%3Aissue); 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.
Please do not use the issue tracker as a forum, do not ask for ETAs and read the issue conversation before posting.
Translations can be contributed via https://www.transifex.com/projects/p/gadgetbridge/resource/strings/ or Translations can be contributed via https://www.transifex.com/projects/p/gadgetbridge/resource/strings/ or
manually. manually.
## Having problems? ## Having problems?
1. Open Gadgetbridge's settings and check the option to write log files 1. Open Gadgetbridge's settings and check the option to write log files

View File

@ -4,9 +4,11 @@ apply plugin: 'pmd'
def ABORT_ON_CHECK_FAILURE=false def ABORT_ON_CHECK_FAILURE=false
tasks.withType(Test) { systemProperty 'MiFirmwareDir', System.getProperty('MiFirmwareDir', null) }
android { android {
compileSdkVersion 23 compileSdkVersion 23
buildToolsVersion "23.0.2" buildToolsVersion "23.0.3"
defaultConfig { defaultConfig {
applicationId "nodomain.freeyourgadget.gadgetbridge" applicationId "nodomain.freeyourgadget.gadgetbridge"
@ -14,8 +16,8 @@ android {
targetSdkVersion 23 targetSdkVersion 23
// note: always bump BOTH versionCode and versionName! // note: always bump BOTH versionCode and versionName!
versionName "0.7.3" versionName "0.9.7"
versionCode 39 versionCode 51
} }
buildTypes { buildTypes {
release { release {
@ -44,12 +46,14 @@ dependencies {
testCompile "org.mockito:mockito-core:1.9.5" testCompile "org.mockito:mockito-core:1.9.5"
compile fileTree(dir: 'libs', include: ['*.jar']) compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:support-v4:23.1.1' compile 'com.android.support:support-v4:23.3.0'
compile 'com.android.support:design:23.3.0'
compile 'com.github.tony19:logback-android-classic:1.1.1-4' compile 'com.github.tony19:logback-android-classic:1.1.1-4'
compile 'org.slf4j:slf4j-api:1.7.7' compile 'org.slf4j:slf4j-api:1.7.7'
compile 'com.github.PhilJay:MPAndroidChart:v2.1.6' compile 'com.github.PhilJay:MPAndroidChart:v2.2.4'
compile 'com.github.pfichtner:durationformatter:0.1.1' compile 'com.github.pfichtner:durationformatter:0.1.1'
compile 'de.cketti.library.changelog:ckchangelog:1.2.2'
} }
check.dependsOn 'findbugs', 'pmd', 'lint' check.dependsOn 'findbugs', 'pmd', 'lint'

View File

@ -24,7 +24,6 @@
<uses-feature <uses-feature
android:name="android.hardware.bluetooth_le" android:name="android.hardware.bluetooth_le"
android:required="false" /> android:required="false" />
<uses-feature <uses-feature
android:name="android.hardware.telephony" android:name="android.hardware.telephony"
android:required="false" /> android:required="false" />
@ -48,25 +47,27 @@
<activity <activity
android:name=".activities.SettingsActivity" android:name=".activities.SettingsActivity"
android:label="@string/title_activity_settings" android:label="@string/title_activity_settings"
android:parentActivityName=".activities.ControlCenter"/> android:parentActivityName=".activities.ControlCenter" />
<activity <activity
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 <activity
android:launchMode="singleTop"
android:name=".activities.AppManagerActivity" android:name=".activities.AppManagerActivity"
android:label="@string/title_activity_appmanager" android:label="@string/title_activity_appmanager"
android:parentActivityName=".activities.ControlCenter"/> android:parentActivityName=".activities.ControlCenter" />
<activity <activity
android:name=".activities.AppBlacklistActivity" android:name=".activities.AppBlacklistActivity"
android:label="@string/title_activity_appblacklist" android:label="@string/title_activity_appblacklist"
android:parentActivityName=".activities.SettingsActivity"/> android:parentActivityName=".activities.SettingsActivity" />
<activity <activity
android:name=".activities.FwAppInstallerActivity" android:name=".activities.FwAppInstallerActivity"
android:label="@string/title_activity_fw_app_insaller" android:label="@string/title_activity_fw_app_insaller"
android:parentActivityName=".activities.ControlCenter"> android:parentActivityName=".activities.ControlCenter">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" /> <data android:mimeType="*/*" />
@ -87,7 +88,6 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\.pbw" /> <data android:pathPattern="/.*\\.pbw" />
<data android:pathPattern="/.*\\..*\\.pbw" /> <data android:pathPattern="/.*\\..*\\.pbw" />
<data android:pathPattern="/.*\\..*\\..*\\.pbw" /> <data android:pathPattern="/.*\\..*\\..*\\.pbw" />
@ -98,7 +98,6 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
<data android:pathPattern="/.*\\.pbz" /> <data android:pathPattern="/.*\\.pbz" />
<data android:pathPattern="/.*\\..*\\.pbz" /> <data android:pathPattern="/.*\\..*\\.pbz" />
<data android:pathPattern="/.*\\..*\\..*\\.pbz" /> <data android:pathPattern="/.*\\..*\\..*\\.pbz" />
@ -109,7 +108,6 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
<data android:pathPattern="/.*\\.pbl" /> <data android:pathPattern="/.*\\.pbl" />
<data android:pathPattern="/.*\\..*\\.pbl" /> <data android:pathPattern="/.*\\..*\\.pbl" />
<data android:pathPattern="/.*\\..*\\..*\\.pbl" /> <data android:pathPattern="/.*\\..*\\..*\\.pbl" />
@ -123,6 +121,7 @@
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<!-- no mimeType filter, needed for CM-derived ROMs? --> <!-- no mimeType filter, needed for CM-derived ROMs? -->
@ -142,7 +141,6 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
<data android:pathPattern="/.*\\.pbw" /> <data android:pathPattern="/.*\\.pbw" />
<data android:pathPattern="/.*\\..*\\.pbw" /> <data android:pathPattern="/.*\\..*\\.pbw" />
<data android:pathPattern="/.*\\..*\\..*\\.pbw" /> <data android:pathPattern="/.*\\..*\\..*\\.pbw" />
@ -153,7 +151,6 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbw" />
<data android:pathPattern="/.*\\.pbz" /> <data android:pathPattern="/.*\\.pbz" />
<data android:pathPattern="/.*\\..*\\.pbz" /> <data android:pathPattern="/.*\\..*\\.pbz" />
<data android:pathPattern="/.*\\..*\\..*\\.pbz" /> <data android:pathPattern="/.*\\..*\\..*\\.pbz" />
@ -164,7 +161,6 @@
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" /> <data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pbz" />
<data android:pathPattern="/.*\\.pbl" /> <data android:pathPattern="/.*\\.pbl" />
<data android:pathPattern="/.*\\..*\\.pbl" /> <data android:pathPattern="/.*\\..*\\.pbl" />
<data android:pathPattern="/.*\\..*\\..*\\.pbl" /> <data android:pathPattern="/.*\\..*\\..*\\.pbl" />
@ -180,10 +176,11 @@
<!-- to receive the firmwares from the donwload content provider --> <!-- to receive the firmwares from the donwload content provider -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/octet-stream" /> <data android:mimeType="application/octet-stream" />
</intent-filter> </intent-filter>
</activity> </activity>
<service <service
@ -237,11 +234,11 @@
android:name=".activities.DebugActivity" android:name=".activities.DebugActivity"
android:label="@string/title_activity_debug" android:label="@string/title_activity_debug"
android:parentActivityName=".activities.ControlCenter" android:parentActivityName=".activities.ControlCenter"
android:windowSoftInputMode="stateHidden"></activity> android:windowSoftInputMode="stateHidden" />
<activity <activity
android:name=".activities.DiscoveryActivity" android:name=".activities.DiscoveryActivity"
android:label="@string/title_activity_discovery" android:label="@string/title_activity_discovery"
android:parentActivityName=".activities.ControlCenter"></activity> android:parentActivityName=".activities.ControlCenter" />
<activity <activity
android:name=".activities.AndroidPairingActivity" android:name=".activities.AndroidPairingActivity"
android:label="@string/title_activity_android_pairing" /> android:label="@string/title_activity_android_pairing" />
@ -251,16 +248,47 @@
<activity <activity
android:name=".activities.charts.ChartsActivity" android:name=".activities.charts.ChartsActivity"
android:label="@string/title_activity_charts" android:label="@string/title_activity_charts"
android:parentActivityName=".activities.ControlCenter"/> android:parentActivityName=".activities.ControlCenter" />
<activity <activity
android:name=".activities.ConfigureAlarms" android:name=".activities.ConfigureAlarms"
android:label="@string/title_activity_set_alarm" android:label="@string/title_activity_set_alarm"
android:parentActivityName=".activities.SettingsActivity"/> android:parentActivityName=".activities.SettingsActivity" />
<activity <activity
android:name=".activities.AlarmDetails" android:name=".activities.AlarmDetails"
android:label="@string/title_activity_alarm_details" android:label="@string/title_activity_alarm_details"
android:parentActivityName=".activities.ConfigureAlarms"/> android:parentActivityName=".activities.ConfigureAlarms" />
<provider android:authorities="com.getpebble.android.provider" android:exported="true" android:name=".contentprovider.PebbleContentProvider" />
<provider
android:name=".contentprovider.PebbleContentProvider"
android:authorities="com.getpebble.android.provider"
android:exported="true" />
<receiver android:name=".SleepAlarmWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="nodomain.freeyourgadget.gadgetbridge.SLEEP_ALARM_WIDGET_CLICK" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/sleep_alarm_widget_info" />
</receiver>
<activity
android:name=".activities.ExternalPebbleJSActivity"
android:label="@string/app_configure"
android:parentActivityName=".activities.AppManagerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter" />
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="gadgetbridge" />
</intent-filter>
</activity>
</application> </application>
</manifest> </manifest>

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<meta name='viewport' content='initial-scale=1.0, maximum-scale=1.0'>
<script type="text/javascript" src="js/Uri.js">
</script>
<script type="text/javascript" src="js/gadgetbridge_boilerplate.js">
</script>
<script type="text/javascript">
</script>
<style>
body {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-touch-callout: none;
}
#config_url,#jsondata {
word-wrap: break-word;
margin: 20px;
}
.btn {
display: inline-block;
position: relative;
height: 32px;
line-height: 32px;
border-radius: 2px;
font-size: 0.9em;
background-color: #eee;
color: #646464;
text-align: center;
border-style: none;
}
.btn:active {
border-style: none;
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2)inset;
transition-delay: 0s;
}
<!-- TODO -->
</style>
</head>
<body onload="" style="width: 100%;">
<div id="step1">
<h2>Url of the configuration:</h2>
<div id="config_url"></div>
<!--<button class="btn" name="show config" value="show config" onclick="Pebble.showConfiguration()" >Show config / URL</button>-->
<button class="btn" name="open config" value="open config" onclick="Pebble.actuallyOpenURL()" >Open configuration website</button>
</div>
<div id="step2">
<h2>Incoming configuration data:</h2>
<div id="jsondata"></div>
<button class="btn" name="send config" value="send config" onclick="Pebble.actuallySendData()" >Send data to pebble</button>
</div>
</body>

View File

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

View File

@ -0,0 +1,137 @@
function loadScript(url, callback) {
// Adding the script tag to the head as suggested before
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
// Then bind the event to the callback function.
// There are several events for cross browser compatibility.
script.onreadystatechange = callback;
script.onload = callback;
// Fire the loading
head.appendChild(script);
}
function getURLVariable(variable, defaultValue) {
// Find all URL parameters
var query = location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
// If the query variable parameter is found, decode it to use and return it for use
if (pair[0] === variable) {
return decodeURIComponent(pair[1]);
}
}
return defaultValue || false;
}
function gbPebble() {
this.configurationURL = null;
this.configurationValues = null;
this.addEventListener = function(e, f) {
if(e == 'ready') {
this.ready = f;
}
if(e == 'showConfiguration') {
this.showConfiguration = f;
}
if(e == 'webviewclosed') {
this.parseconfig = f;
}
if(e == 'appmessage') {
this.appmessage = f;
}
}
this.removeEventListener = function(e, f) {
if(e == 'ready') {
this.ready = null;
}
if(e == 'showConfiguration') {
this.showConfiguration = null;
}
if(e == 'webviewclosed') {
this.parseconfig = null;
}
if(e == 'appmessage') {
this.appmessage = null;
}
}
this.actuallyOpenURL = function() {
window.open(this.configurationURL.toString(), "config");
}
this.actuallySendData = function() {
GBjs.sendAppMessage(this.configurationValues);
}
//needs to be called like this because of original Pebble function name
this.openURL = function(url) {
if (url.lastIndexOf("http", 0) === 0) {
document.getElementById("config_url").innerHTML=url;
var UUID = GBjs.getAppUUID();
this.configurationURL = new Uri(url).addQueryParam("return_to", "gadgetbridge://"+UUID+"?config=true&json=");
} else {
//TODO: add custom return_to
location.href = url;
}
}
this.getActiveWatchInfo = function() {
return JSON.parse(GBjs.getActiveWatchInfo());
}
this.sendAppMessage = function (dict, callbackAck, callbackNack){
try {
this.configurationValues = JSON.stringify(dict);
document.getElementById("jsondata").innerHTML=this.configurationValues;
return callbackAck;
}
catch (e) {
GBjs.gbLog("sendAppMessage failed");
return callbackNack;
}
}
this.getAccountToken = function() {
return '';
}
this.getWatchToken = function() {
return GBjs.getWatchToken();
}
this.showSimpleNotificationOnPebble = function(title, body) {
GBjs.gbLog("app wanted to show: " + title + " body: "+ body);
}
this.ready = function() {
}
}
var Pebble = new gbPebble();
var jsConfigFile = GBjs.getAppConfigurationFile();
if (jsConfigFile != null) {
loadScript(jsConfigFile, function() {
if (getURLVariable('config') == 'true') {
document.getElementById('step1').style.display="none";
var json_string = unescape(getURLVariable('json'));
var t = new Object();
t.response = json_string;
if (json_string != '')
Pebble.parseconfig(t);
} else {
document.getElementById('step2').style.display="none";
Pebble.ready();
Pebble.showConfiguration();
}
});
}

View File

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg2"
viewBox="0 0 1814.1732 1814.1732"
height="512mm"
width="512mm"
inkscape:version="0.91 r13725"
sodipodi:docname="211ad552-a817-11e5-98b9-385bef8cd413.svg">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1600"
inkscape:window-height="876"
id="namedview4212"
showgrid="false"
inkscape:zoom="0.4040408"
inkscape:cx="498.78758"
inkscape:cy="883.13571"
inkscape:window-x="1024"
inkscape:window-y="24"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<defs
id="defs4">
<marker
orient="auto"
refY="0.0"
refX="0.0"
id="marker4512"
style="overflow:visible">
<path
id="path4514"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#1976d2;stroke-width:1pt;stroke-opacity:1;fill:#ffffff;fill-opacity:1"
transform="scale(0.8) translate(12.5,0)" />
</marker>
<marker
style="overflow:visible"
id="Arrow1Lstart"
refX="0.0"
refY="0.0"
orient="auto">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#1976d2;stroke-width:1pt;stroke-opacity:1;fill:#ffffff;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4208" />
</marker>
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<circle
style="fill:#1976d2;fill-opacity:1;stroke:none;stroke-width:5.9000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4178"
cx="899.03583"
cy="935.34052"
r="652.55853" />
<path
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:0.24025975;stroke:none;stroke-width:5.9000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 1180.6138,761.25155 -397.79692,586.48635 230.40242,230.4024 a 652.55853,652.55853 0 0 0 515.1523,-469.1328 L 1180.6138,761.25155 Z"
id="path4178-3" />
<path
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#1976d2;stroke-width:13.60000038;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 703.29568,640.10033 c 0,0 -62.09786,90.61312 -84.48779,144.65142 -26.49785,122.73311 44.15742,168.66683 151.63581,319.05845 l -2.08379,245.8099 251.87809,0 0,-245.8099 c 0,0 77.6015,-28.5779 110.2687,-109.23097 30.2773,-74.75286 27.0683,-193.76648 27.0683,-193.76648 z"
id="path4176" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-width:13.60000038;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4136"
width="109.0837"
height="219.8989"
x="1289.9961"
y="43.667068"
ry="43.04707"
transform="matrix(0.8660254,0.5,-0.5,0.8660254,0,0)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-width:13.60000038;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-5"
width="109.0837"
height="252.79712"
x="1180.0483"
y="40.667068"
ry="49.487179"
transform="matrix(0.8660254,0.5,-0.5,0.8660254,0,0)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-width:13.60000038;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-8"
width="109.0837"
height="297.81583"
x="1070.1006"
y="37.667068"
ry="58.299976"
transform="matrix(0.8660254,0.5,-0.5,0.8660254,0,0)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-width:13.60000038;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-1"
width="109.0837"
height="219.8989"
x="958.4209"
y="35.667068"
ry="43.04707"
transform="matrix(0.8660254,0.5,-0.5,0.8660254,0,0)" />
<path
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#1976d2;stroke-width:12.19999981;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 707.67725,640.85267 143.17855,38.54881 c 0,0 39.4733,34.66779 27.90054,81.29964 -10.45038,42.10926 -58.66289,44.51639 -58.66289,44.51639 L 705.87743,773.82104"
id="path4186" />
<rect
style="fill:#ff9800;fill-opacity:1;stroke:#ff9800;stroke-width:12.48825073;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0"
id="rect4628"
width="314.25906"
height="96.066086"
x="738.79462"
y="1163.3024"
ry="7.8193355" />
<rect
style="fill:#1976d2;fill-opacity:1;stroke:#1976d2;stroke-width:8.70396328;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4624"
width="171.0025"
height="47.28043"
x="810.42285"
y="1187.136"
ry="15.011105" />
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

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

View File

@ -6,11 +6,13 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Build; import android.os.Build;
import android.os.Build.VERSION; import android.os.Build.VERSION;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.util.Log; import android.util.Log;
import android.util.TypedValue;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -22,14 +24,19 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler; import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBConstants; import nodomain.freeyourgadget.gadgetbridge.database.DBConstants;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
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.externalevents.BluetoothConnectReceiver; //import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver;
@ -45,7 +52,13 @@ public class GBApplication extends Application {
private static final Lock dbLock = new ReentrantLock(); private static final Lock dbLock = new ReentrantLock();
private static DeviceService deviceService; private static DeviceService deviceService;
private static SharedPreferences sharedPrefs; private static SharedPreferences sharedPrefs;
private static final String PREFS_VERSION = "shared_preferences_version";
//if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version
private static final int CURRENT_PREFS_VERSION = 2;
private static LimitedQueue mIDSenderLookup = new LimitedQueue(16); private static LimitedQueue mIDSenderLookup = new LimitedQueue(16);
private static Appender<ILoggingEvent> fileLogger;
private static Prefs prefs;
private static GBPrefs gbPrefs;
public static final String ACTION_QUIT public static final String ACTION_QUIT
= "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit"; = "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit";
@ -79,10 +92,16 @@ public class GBApplication extends Application {
super.onCreate(); super.onCreate();
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs = new Prefs(sharedPrefs);
gbPrefs = new GBPrefs(prefs);
// don't do anything here before we set up logging, otherwise // don't do anything here before we set up logging, otherwise
// slf4j may be implicitly initialized before we properly configured it. // slf4j may be implicitly initialized before we properly configured it.
setupLogging(); setupLogging(isFileLoggingEnabled());
if (getPrefsFileVersion() != CURRENT_PREFS_VERSION) {
migratePrefs(getPrefsFileVersion());
}
setupExceptionHandler(); setupExceptionHandler();
// For debugging problems with the logback configuration // For debugging problems with the logback configuration
@ -111,35 +130,71 @@ public class GBApplication extends Application {
} }
public static boolean isFileLoggingEnabled() { public static boolean isFileLoggingEnabled() {
return sharedPrefs.getBoolean("log_to_file", false); return prefs.getBoolean("log_to_file", false);
} }
private void setupLogging() { public static void setupLogging(boolean enable) {
if (isFileLoggingEnabled()) { try {
try { if (fileLogger == null) {
File dir = FileUtils.getExternalFilesDir(); File dir = FileUtils.getExternalFilesDir();
// used by assets/logback.xml since the location cannot be statically determined // used by assets/logback.xml since the location cannot be statically determined
System.setProperty("GB_LOGFILES_DIR", dir.getAbsolutePath()); System.setProperty("GB_LOGFILES_DIR", dir.getAbsolutePath());
getLogger().info("Gadgetbridge version: " + BuildConfig.VERSION_NAME); rememberFileLogger();
} catch (IOException ex) {
Log.e("GBApplication", "External files dir not available, cannot log to file", ex);
removeFileLogger();
} }
} else { if (enable) {
removeFileLogger(); startFileLogger();
} else {
stopFileLogger();
}
getLogger().info("Gadgetbridge version: " + BuildConfig.VERSION_NAME);
} catch (IOException ex) {
Log.e("GBApplication", "External files dir not available, cannot log to file", ex);
stopFileLogger();
} }
} }
private void removeFileLogger() { private static void startFileLogger() {
if (fileLogger != null && !fileLogger.isStarted()) {
addFileLogger(fileLogger);
fileLogger.start();
}
}
private static void stopFileLogger() {
if (fileLogger != null && fileLogger.isStarted()) {
fileLogger.stop();
removeFileLogger(fileLogger);
}
}
private static void rememberFileLogger() {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
fileLogger = root.getAppender("FILE");
}
private static void addFileLogger(Appender<ILoggingEvent> fileLogger) {
try { try {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
root.detachAppender("FILE"); if (!root.isAttached(fileLogger)) {
root.addAppender(fileLogger);
}
} catch (Throwable ex) {
Log.e("GBApplication", "Error adding logger FILE appender", ex);
}
}
private static void removeFileLogger(Appender<ILoggingEvent> fileLogger) {
try {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
if (root.isAttached(fileLogger)) {
root.detachAppender(fileLogger);
}
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e("GBApplication", "Error removing logger FILE appender", ex); Log.e("GBApplication", "Error removing logger FILE appender", ex);
} }
} }
private Logger getLogger() { private static Logger getLogger() {
return LoggerFactory.getLogger(GBApplication.class); return LoggerFactory.getLogger(GBApplication.class);
} }
@ -188,10 +243,6 @@ public class GBApplication extends Application {
dbLock.unlock(); dbLock.unlock();
} }
public static boolean isRunningOnKitkatOrLater() {
return VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
}
public static boolean isRunningLollipopOrLater() { public static boolean isRunningLollipopOrLater() {
return VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; return VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
} }
@ -242,7 +293,84 @@ public class GBApplication extends Application {
return result; return result;
} }
private int getPrefsFileVersion() {
try {
return Integer.parseInt(sharedPrefs.getString(PREFS_VERSION, "0")); //0 is legacy
} catch (Exception e) {
//in version 1 this was an int
return 1;
}
}
private void migratePrefs(int oldVersion) {
SharedPreferences.Editor editor = sharedPrefs.edit();
switch (oldVersion) {
case 0:
String legacyGender = sharedPrefs.getString("mi_user_gender", null);
String legacyHeight = sharedPrefs.getString("mi_user_height_cm", null);
String legacyWeigth = sharedPrefs.getString("mi_user_weight_kg", null);
String legacyYOB = sharedPrefs.getString("mi_user_year_of_birth", null);
if (legacyGender != null) {
int gender = "male".equals(legacyGender) ? 1 : "female".equals(legacyGender) ? 0 : 2;
editor.putString(ActivityUser.PREF_USER_GENDER, Integer.toString(gender));
editor.remove("mi_user_gender");
}
if (legacyHeight != null) {
editor.putString(ActivityUser.PREF_USER_HEIGHT_CM, legacyHeight);
editor.remove("mi_user_height_cm");
}
if (legacyWeigth != null) {
editor.putString(ActivityUser.PREF_USER_WEIGHT_KG, legacyWeigth);
editor.remove("mi_user_weight_kg");
}
if (legacyYOB != null) {
editor.putString(ActivityUser.PREF_USER_YEAR_OF_BIRTH, legacyYOB);
editor.remove("mi_user_year_of_birth");
}
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
break;
case 1:
//migrate the integer version of gender introduced in version 1 to a string value, needed for the way Android accesses the shared preferences
int legacyGender_1 = 2;
try {
legacyGender_1 = sharedPrefs.getInt(ActivityUser.PREF_USER_GENDER, 2);
} catch (Exception e) {
Log.e(TAG, "Could not access legacy activity gender", e);
}
editor.putString(ActivityUser.PREF_USER_GENDER, Integer.toString(legacyGender_1));
//also silently migrate the version to a string value
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
break;
}
editor.apply();
}
public static LimitedQueue getIDSenderLookup() { public static LimitedQueue getIDSenderLookup() {
return mIDSenderLookup; return mIDSenderLookup;
} }
public static boolean isDarkThemeEnabled() {
return prefs.getString("pref_key_theme", context.getString(R.string.pref_theme_value_light)).equals(context.getString(R.string.pref_theme_value_dark));
}
public static int getTextColor(Context context) {
TypedValue typedValue = new TypedValue();
Resources.Theme theme = context.getTheme();
theme.resolveAttribute(android.R.attr.textColor, typedValue, true);
return typedValue.data;
}
public static int getBackgroundColor(Context context) {
TypedValue typedValue = new TypedValue();
Resources.Theme theme = context.getTheme();
theme.resolveAttribute(android.R.attr.background, typedValue, true);
return typedValue.data;
}
public static Prefs getPrefs() {
return prefs;
}
public static GBPrefs getGBPrefs() {
return gbPrefs;
}
} }

View File

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

View File

@ -1,7 +1,6 @@
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentPagerAdapter;
@ -18,7 +17,7 @@ import android.support.v4.app.FragmentPagerAdapter;
* *
* @see AbstractGBFragment * @see AbstractGBFragment
*/ */
public abstract class AbstractGBFragmentActivity extends FragmentActivity { public abstract class AbstractGBFragmentActivity extends GBActivity {
/** /**
* The {@link android.support.v4.view.PagerAdapter} that will provide * The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a * fragments for each of the sections. We use a

View File

@ -1,16 +1,30 @@
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceActivity; import android.preference.PreferenceActivity;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.v4.app.NavUtils; import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
import android.text.InputType;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
/** /**
* A settings activity with support for preferences directly displaying their value. * A settings activity with support for preferences directly displaying their value.
* If you combine such preferences with a custom OnPreferenceChangeListener, you have * If you combine such preferences with a custom OnPreferenceChangeListener, you have
@ -20,6 +34,7 @@ import org.slf4j.LoggerFactory;
public abstract class AbstractSettingsActivity extends PreferenceActivity { public abstract class AbstractSettingsActivity extends PreferenceActivity {
private static final Logger LOG = LoggerFactory.getLogger(AbstractSettingsActivity.class); private static final Logger LOG = LoggerFactory.getLogger(AbstractSettingsActivity.class);
private AppCompatDelegate delegate;
/** /**
* A preference value change listener that updates the preference's summary * A preference value change listener that updates the preference's summary
@ -28,12 +43,20 @@ public abstract class AbstractSettingsActivity extends PreferenceActivity {
private static class SimpleSetSummaryOnChangeListener implements Preference.OnPreferenceChangeListener { private static class SimpleSetSummaryOnChangeListener implements Preference.OnPreferenceChangeListener {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object value) { public boolean onPreferenceChange(Preference preference, Object value) {
if (preference instanceof EditTextPreference) {
if (((EditTextPreference) preference).getEditText().getKeyListener().getInputType() == InputType.TYPE_CLASS_NUMBER) {
if ("".equals(String.valueOf(value))) {
// reject empty numeric input
return false;
}
}
}
updateSummary(preference, value); updateSummary(preference, value);
return true; return true;
} }
public void updateSummary(Preference preference, Object value) { public void updateSummary(Preference preference, Object value) {
String stringValue = value.toString(); String stringValue = String.valueOf(value);
if (preference instanceof ListPreference) { if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in // For list preferences, look up the correct display value in
@ -56,15 +79,15 @@ public abstract class AbstractSettingsActivity extends PreferenceActivity {
} }
private static class ExtraSetSummaryOnChangeListener extends SimpleSetSummaryOnChangeListener { private static class ExtraSetSummaryOnChangeListener extends SimpleSetSummaryOnChangeListener {
private final Preference.OnPreferenceChangeListener delegate; private final Preference.OnPreferenceChangeListener prefChangeListener;
public ExtraSetSummaryOnChangeListener(Preference.OnPreferenceChangeListener delegate) { public ExtraSetSummaryOnChangeListener(Preference.OnPreferenceChangeListener prefChangeListener) {
this.delegate = delegate; this.prefChangeListener = prefChangeListener;
} }
@Override @Override
public boolean onPreferenceChange(Preference preference, Object value) { public boolean onPreferenceChange(Preference preference, Object value) {
boolean result = delegate.onPreferenceChange(preference, value); boolean result = prefChangeListener.onPreferenceChange(preference, value);
if (result) { if (result) {
return super.onPreferenceChange(preference, value); return super.onPreferenceChange(preference, value);
} }
@ -74,11 +97,22 @@ public abstract class AbstractSettingsActivity extends PreferenceActivity {
private static final SimpleSetSummaryOnChangeListener sBindPreferenceSummaryToValueListener = new SimpleSetSummaryOnChangeListener(); private static final SimpleSetSummaryOnChangeListener sBindPreferenceSummaryToValueListener = new SimpleSetSummaryOnChangeListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
if (GBApplication.isDarkThemeEnabled()) {
setTheme(R.style.GadgetbridgeThemeDark);
} else {
setTheme(R.style.GadgetbridgeTheme);
}
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Override @Override
protected void onPostCreate(Bundle savedInstanceState) { protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState); super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true);
for (String prefKey : getPreferenceKeysWithSummary()) { for (String prefKey : getPreferenceKeysWithSummary()) {
final Preference pref = findPreference(prefKey); final Preference pref = findPreference(prefKey);
@ -90,6 +124,67 @@ public abstract class AbstractSettingsActivity extends PreferenceActivity {
} }
} }
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
/** /**
* Subclasses should reimplement this to return the keys of those * Subclasses should reimplement this to return the keys of those
* preferences which should print its values as a summary below the * preferences which should print its values as a summary below the
@ -141,4 +236,19 @@ public abstract class AbstractSettingsActivity extends PreferenceActivity {
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
private AppCompatDelegate getDelegate() {
if (delegate == null) {
delegate = AppCompatDelegate.create(this, null);
}
return delegate;
}
} }

View File

@ -1,6 +1,5 @@
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.text.format.DateFormat; import android.text.format.DateFormat;
@ -12,7 +11,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm; import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
public class AlarmDetails extends Activity { public class AlarmDetails extends GBActivity {
private GBAlarm alarm; private GBAlarm alarm;
private TimePicker timePicker; private TimePicker timePicker;

View File

@ -1,11 +1,10 @@
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
public class AndroidPairingActivity extends Activity { public class AndroidPairingActivity extends GBActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {

View File

@ -1,15 +1,12 @@
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils; import android.support.v4.app.NavUtils;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -28,13 +25,14 @@ import org.slf4j.LoggerFactory;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
public class AppBlacklistActivity extends Activity { public class AppBlacklistActivity extends GBActivity {
private static final Logger LOG = LoggerFactory.getLogger(AppBlacklistActivity.class); private static final Logger LOG = LoggerFactory.getLogger(AppBlacklistActivity.class);
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@ -47,20 +45,41 @@ public class AppBlacklistActivity extends Activity {
} }
}; };
private SharedPreferences sharedPrefs; private IdentityHashMap<ApplicationInfo, String> nameMap;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_appblacklist); setContentView(R.layout.activity_appblacklist);
getActionBar().setDisplayHomeAsUpEnabled(true);
final PackageManager pm = getPackageManager(); final PackageManager pm = getPackageManager();
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
final List<ApplicationInfo> packageList = pm.getInstalledApplications(PackageManager.GET_META_DATA); final List<ApplicationInfo> packageList = pm.getInstalledApplications(PackageManager.GET_META_DATA);
ListView appListView = (ListView) findViewById(R.id.appListView); ListView appListView = (ListView) findViewById(R.id.appListView);
// sort the package list by label and blacklist status
nameMap = new IdentityHashMap<>(packageList.size());
for (ApplicationInfo ai : packageList) {
CharSequence name = pm.getApplicationLabel(ai);
if (name == null) {
name = ai.packageName;
}
if (GBApplication.blacklist.contains(ai.packageName)) {
// sort blacklisted first by prefixing with a '!'
name = "!" + name;
}
nameMap.put(ai, name.toString());
}
Collections.sort(packageList, new Comparator<ApplicationInfo>() {
@Override
public int compare(ApplicationInfo ai1, ApplicationInfo ai2) {
final String s1 = nameMap.get(ai1);
final String s2 = nameMap.get(ai2);
return s1.compareTo(s2);
}
});
final ArrayAdapter<ApplicationInfo> adapter = new ArrayAdapter<ApplicationInfo>(this, R.layout.item_with_checkbox, packageList) { final ArrayAdapter<ApplicationInfo> adapter = new ArrayAdapter<ApplicationInfo>(this, R.layout.item_with_checkbox, packageList) {
@Override @Override
public View getView(int position, View view, ViewGroup parent) { public View getView(int position, View view, ViewGroup parent) {
@ -76,27 +95,11 @@ public class AppBlacklistActivity extends Activity {
CheckBox checkbox = (CheckBox) view.findViewById(R.id.item_checkbox); CheckBox checkbox = (CheckBox) view.findViewById(R.id.item_checkbox);
deviceAppVersionAuthorLabel.setText(appInfo.packageName); deviceAppVersionAuthorLabel.setText(appInfo.packageName);
deviceAppNameLabel.setText(appInfo.loadLabel(pm)); deviceAppNameLabel.setText(nameMap.get(appInfo));
deviceImageView.setImageDrawable(appInfo.loadIcon(pm)); deviceImageView.setImageDrawable(appInfo.loadIcon(pm));
checkbox.setChecked(GBApplication.blacklist.contains(appInfo.packageName)); checkbox.setChecked(GBApplication.blacklist.contains(appInfo.packageName));
Collections.sort(packageList, new Comparator<ApplicationInfo>() {
@Override
public int compare(ApplicationInfo ai1, ApplicationInfo ai2) {
boolean blacklisted1 = GBApplication.blacklist.contains(ai1.packageName);
boolean blacklisted2 = GBApplication.blacklist.contains(ai2.packageName);
if ((blacklisted1 && blacklisted2) || (!blacklisted1 && !blacklisted2)) {
// both blacklisted or both not blacklisted = sort by alphabet
return ai1.packageName.compareTo(ai2.packageName);
} else if (blacklisted1) {
return -1;
} else {
return 1;
}
}
});
return view; return view;
} }
}; };

View File

@ -1,14 +1,11 @@
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils; import android.support.v4.app.NavUtils;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.view.ContextMenu; import android.view.ContextMenu;
@ -30,12 +27,15 @@ 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.adapter.GBDeviceAppAdapter; import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAppAdapter;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol; import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class AppManagerActivity extends Activity { public class AppManagerActivity extends GBActivity {
public static final String ACTION_REFRESH_APPLIST public static final String ACTION_REFRESH_APPLIST
= "nodomain.freeyourgadget.gadgetbridge.appmanager.action.refresh_applist"; = "nodomain.freeyourgadget.gadgetbridge.appmanager.action.refresh_applist";
private static final Logger LOG = LoggerFactory.getLogger(AppManagerActivity.class); private static final Logger LOG = LoggerFactory.getLogger(AppManagerActivity.class);
@ -58,7 +58,7 @@ public class AppManagerActivity extends Activity {
appList.add(new GBDeviceApp(uuid, appName, appCreator, "", appType)); appList.add(new GBDeviceApp(uuid, appName, appCreator, "", appType));
} }
if (sharedPrefs.getBoolean("pebble_force_untested", false)) { if (prefs.getBoolean("pebble_force_untested", false)) {
appList.addAll(getSystemApps()); appList.addAll(getSystemApps());
} }
@ -67,17 +67,20 @@ public class AppManagerActivity extends Activity {
} }
}; };
private SharedPreferences sharedPrefs; private Prefs prefs;
private final List<GBDeviceApp> appList = new ArrayList<>(); private final List<GBDeviceApp> appList = new ArrayList<>();
private GBDeviceAppAdapter mGBDeviceAppAdapter; private GBDeviceAppAdapter mGBDeviceAppAdapter;
private GBDeviceApp selectedApp = null; private GBDeviceApp selectedApp = null;
private GBDevice mGBDevice = null;
private List<GBDeviceApp> getSystemApps() { private List<GBDeviceApp> getSystemApps() {
List<GBDeviceApp> systemApps = new ArrayList<>(); List<GBDeviceApp> systemApps = new ArrayList<>();
systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getHardwareVersion()))) {
systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
}
return systemApps; return systemApps;
} }
@ -97,11 +100,14 @@ public class AppManagerActivity extends Activity {
for (File file : files) { for (File file : files) {
if (file.getName().endsWith(".pbw")) { if (file.getName().endsWith(".pbw")) {
String baseName = file.getName().substring(0, file.getName().length() - 4); String baseName = file.getName().substring(0, file.getName().length() - 4);
//metadata
File jsonFile = new File(cachePath, baseName + ".json"); File jsonFile = new File(cachePath, baseName + ".json");
//configuration
File configFile = new File(cachePath, baseName + "_config.js");
try { try {
String jsonstring = FileUtils.getStringFromFile(jsonFile); String jsonstring = FileUtils.getStringFromFile(jsonFile);
JSONObject json = new JSONObject(jsonstring); JSONObject json = new JSONObject(jsonstring);
cachedAppList.add(new GBDeviceApp(json)); cachedAppList.add(new GBDeviceApp(json, configFile.exists()));
} catch (Exception e) { } catch (Exception e) {
LOG.warn("could not read json file for " + baseName, e.getMessage(), e); LOG.warn("could not read json file for " + baseName, e.getMessage(), e);
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), baseName, "N/A", "", GBDeviceApp.Type.UNKNOWN)); cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), baseName, "N/A", "", GBDeviceApp.Type.UNKNOWN));
@ -116,10 +122,16 @@ public class AppManagerActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); 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");
}
prefs = GBApplication.getPrefs();
setContentView(R.layout.activity_appmanager); setContentView(R.layout.activity_appmanager);
getActionBar().setDisplayHomeAsUpEnabled(true);
ListView appListView = (ListView) findViewById(R.id.appListView); ListView appListView = (ListView) findViewById(R.id.appListView);
mGBDeviceAppAdapter = new GBDeviceAppAdapter(this, appList); mGBDeviceAppAdapter = new GBDeviceAppAdapter(this, appList);
@ -137,7 +149,7 @@ public class AppManagerActivity extends Activity {
appList.addAll(getCachedApps()); appList.addAll(getCachedApps());
if (sharedPrefs.getBoolean("pebble_force_untested", false)) { if (prefs.getBoolean("pebble_force_untested", false)) {
appList.addAll(getSystemApps()); appList.addAll(getSystemApps());
} }
@ -157,24 +169,29 @@ public class AppManagerActivity extends Activity {
AdapterView.AdapterContextMenuInfo acmi = (AdapterView.AdapterContextMenuInfo) menuInfo; AdapterView.AdapterContextMenuInfo acmi = (AdapterView.AdapterContextMenuInfo) menuInfo;
selectedApp = appList.get(acmi.position); selectedApp = appList.get(acmi.position);
if (!selectedApp.isInCache() && !PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) { if (!selectedApp.isInCache()) {
menu.removeItem(R.id.appmanager_app_reinstall); menu.removeItem(R.id.appmanager_app_reinstall);
} }
if (!PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) {
menu.removeItem(R.id.appmanager_health_activate);
menu.removeItem(R.id.appmanager_health_deactivate);
} else if (PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) {
menu.removeItem(R.id.appmanager_app_delete);
}
if (!selectedApp.isConfigurable()) {
menu.removeItem(R.id.appmanager_app_configure);
}
menu.setHeaderTitle(selectedApp.getName()); menu.setHeaderTitle(selectedApp.getName());
} }
@Override @Override
public boolean onContextItemSelected(MenuItem item) { public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.appmanager_health_deactivate:
case R.id.appmanager_app_delete: case R.id.appmanager_app_delete:
GBApplication.deviceService().onAppDelete(selectedApp.getUUID()); GBApplication.deviceService().onAppDelete(selectedApp.getUUID());
return true; return true;
case R.id.appmanager_app_reinstall: case R.id.appmanager_app_reinstall:
if (PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) {
GBApplication.deviceService().onInstallApp(Uri.parse("fake://health"));
return true;
}
File cachePath; File cachePath;
try { try {
cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID() + ".pbw"); cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID() + ".pbw");
@ -184,6 +201,17 @@ public class AppManagerActivity extends Activity {
} }
GBApplication.deviceService().onInstallApp(Uri.fromFile(cachePath)); GBApplication.deviceService().onInstallApp(Uri.fromFile(cachePath));
return true; return true;
case R.id.appmanager_health_activate:
GBApplication.deviceService().onInstallApp(Uri.parse("fake://health"));
return true;
case R.id.appmanager_app_configure:
GBApplication.deviceService().onAppStart(selectedApp.getUUID(), true);
Intent startIntent = new Intent(getApplicationContext(), ExternalPebbleJSActivity.class);
startIntent.putExtra("app_uuid", selectedApp.getUUID());
startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice);
startActivity(startIntent);
return true;
default: default:
return super.onContextItemSelected(item); return super.onContextItemSelected(item);
} }

View File

@ -1,11 +1,9 @@
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.ListActivity;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.ListView;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
@ -16,11 +14,12 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.GBAlarmListAdapter; import nodomain.freeyourgadget.gadgetbridge.adapter.GBAlarmListAdapter;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm; import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ALARMS; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ALARMS;
public class ConfigureAlarms extends ListActivity { public class ConfigureAlarms extends GBActivity {
private static final int REQ_CONFIGURE_ALARM = 1; private static final int REQ_CONFIGURE_ALARM = 1;
@ -33,19 +32,19 @@ public class ConfigureAlarms extends ListActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_configure_alarms); setContentView(R.layout.activity_configure_alarms);
getActionBar().setDisplayHomeAsUpEnabled(true);
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); Prefs prefs = GBApplication.getPrefs();
preferencesAlarmListSet = sharedPrefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>()); preferencesAlarmListSet = prefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>());
if (preferencesAlarmListSet.isEmpty()) { if (preferencesAlarmListSet.isEmpty()) {
//initialize the preferences //initialize the preferences
preferencesAlarmListSet = new HashSet<>(Arrays.asList(GBAlarm.DEFAULT_ALARMS)); preferencesAlarmListSet = new HashSet<>(Arrays.asList(GBAlarm.DEFAULT_ALARMS));
sharedPrefs.edit().putStringSet(PREF_MIBAND_ALARMS, preferencesAlarmListSet).apply(); prefs.getPreferences().edit().putStringSet(PREF_MIBAND_ALARMS, preferencesAlarmListSet).apply();
} }
mGBAlarmListAdapter = new GBAlarmListAdapter(this, preferencesAlarmListSet); mGBAlarmListAdapter = new GBAlarmListAdapter(this, preferencesAlarmListSet);
setListAdapter(mGBAlarmListAdapter); ListView listView = (ListView) findViewById(R.id.alarm_list);
listView.setAdapter(mGBAlarmListAdapter);
updateAlarmsFromPrefs(); updateAlarmsFromPrefs();
} }
@ -66,9 +65,9 @@ public class ConfigureAlarms extends ListActivity {
} }
private void updateAlarmsFromPrefs() { private void updateAlarmsFromPrefs() {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); Prefs prefs = GBApplication.getPrefs();
preferencesAlarmListSet = sharedPrefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>()); preferencesAlarmListSet = prefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>());
int reservedSlots = Integer.parseInt(sharedPrefs.getString(MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, "0")); int reservedSlots = prefs.getInt(MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, 0);
mGBAlarmListAdapter.setAlarmList(preferencesAlarmListSet, reservedSlots); mGBAlarmListAdapter.setAlarmList(preferencesAlarmListSet, reservedSlots);
mGBAlarmListAdapter.notifyDataSetChanged(); mGBAlarmListAdapter.notifyDataSetChanged();

View File

@ -1,5 +1,7 @@
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
@ -8,9 +10,12 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.widget.SwipeRefreshLayout; import android.support.v4.widget.SwipeRefreshLayout;
import android.view.ContextMenu; import android.view.ContextMenu;
@ -18,6 +23,7 @@ import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -29,6 +35,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import de.cketti.library.changelog.ChangeLog;
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.charts.ChartsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
@ -37,8 +44,9 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
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; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class ControlCenter extends Activity { public class ControlCenter extends GBActivity {
private static final Logger LOG = LoggerFactory.getLogger(ControlCenter.class); private static final Logger LOG = LoggerFactory.getLogger(ControlCenter.class);
@ -46,6 +54,9 @@ public class ControlCenter extends Activity {
= "nodomain.freeyourgadget.gadgetbridge.controlcenter.action.set_version"; = "nodomain.freeyourgadget.gadgetbridge.controlcenter.action.set_version";
private TextView hintTextView; private TextView hintTextView;
private FloatingActionButton fab;
private ImageView background;
private SwipeRefreshLayout swipeLayout; private SwipeRefreshLayout swipeLayout;
private GBDeviceAdapter mGBDeviceAdapter; private GBDeviceAdapter mGBDeviceAdapter;
private GBDevice selectedDevice = null; private GBDevice selectedDevice = null;
@ -116,15 +127,26 @@ public class ControlCenter extends Activity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_controlcenter); setContentView(R.layout.activity_controlcenter);
hintTextView = (TextView) findViewById(R.id.hintTextView); hintTextView = (TextView) findViewById(R.id.hintTextView);
ListView deviceListView = (ListView) findViewById(R.id.deviceListView); ListView deviceListView = (ListView) findViewById(R.id.deviceListView);
fab = (FloatingActionButton) findViewById(R.id.fab);
background = (ImageView) findViewById(R.id.no_items_bg);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
launchDiscoveryActivity();
}
});
mGBDeviceAdapter = new GBDeviceAdapter(this, deviceList); mGBDeviceAdapter = new GBDeviceAdapter(this, deviceList);
deviceListView.setAdapter(this.mGBDeviceAdapter); deviceListView.setAdapter(this.mGBDeviceAdapter);
deviceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { deviceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override @Override
public void onItemClick(AdapterView parent, View v, int position, long id) { public void onItemClick(AdapterView parent, View v, int position, long id) {
GBDevice gbDevice = deviceList.get(position); GBDevice gbDevice = deviceList.get(position);
if (gbDevice.isConnected()) { if (gbDevice.isInitialized()) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice); DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
Class<? extends Activity> primaryActivity = coordinator.getPrimaryActivity(); Class<? extends Activity> primaryActivity = coordinator.getPrimaryActivity();
if (primaryActivity != null) { if (primaryActivity != null) {
@ -161,17 +183,25 @@ public class ControlCenter extends Activity {
/* /*
* Ask for permission to intercept notifications on first run. * Ask for permission to intercept notifications on first run.
*/ */
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); Prefs prefs = GBApplication.getPrefs();
if (sharedPrefs.getBoolean("firstrun", true)) { if (prefs.getBoolean("firstrun", true)) {
sharedPrefs.edit().putBoolean("firstrun", false).apply(); prefs.getPreferences().edit().putBoolean("firstrun", false).apply();
Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
startActivity(enableIntent); startActivity(enableIntent);
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
checkAndRequestPermissions();
}
ChangeLog cl = new ChangeLog(this);
if (cl.isFirstRun()) {
cl.getLogDialog().show();
}
GBApplication.deviceService().start(); GBApplication.deviceService().start();
enableSwipeRefresh(selectedDevice); enableSwipeRefresh(selectedDevice);
if (GB.isBluetoothEnabled() && deviceList.isEmpty()) { if (GB.isBluetoothEnabled() && deviceList.isEmpty() && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// start discovery when no devices are present
startActivity(new Intent(this, DiscoveryActivity.class)); startActivity(new Intent(this, DiscoveryActivity.class));
} else { } else {
GBApplication.deviceService().requestDeviceInfo(); GBApplication.deviceService().requestDeviceInfo();
@ -196,6 +226,9 @@ public class ControlCenter extends Activity {
if (!coordinator.supportsScreenshots()) { if (!coordinator.supportsScreenshots()) {
menu.removeItem(R.id.controlcenter_take_screenshot); menu.removeItem(R.id.controlcenter_take_screenshot);
} }
if (!coordinator.supportsAlarmConfiguration()) {
menu.removeItem(R.id.controlcenter_configure_alarms);
}
if (selectedDevice.getState() == GBDevice.State.NOT_CONNECTED) { if (selectedDevice.getState() == GBDevice.State.NOT_CONNECTED) {
menu.removeItem(R.id.controlcenter_disconnect); menu.removeItem(R.id.controlcenter_disconnect);
@ -314,15 +347,19 @@ public class ControlCenter extends Activity {
Intent quitIntent = new Intent(GBApplication.ACTION_QUIT); Intent quitIntent = new Intent(GBApplication.ACTION_QUIT);
LocalBroadcastManager.getInstance(this).sendBroadcast(quitIntent); LocalBroadcastManager.getInstance(this).sendBroadcast(quitIntent);
return true; return true;
case R.id.action_discover:
Intent discoverIntent = new Intent(this, DiscoveryActivity.class);
startActivity(discoverIntent);
return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
private void launchDiscoveryActivity() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
startActivity(new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS));
} else {
startActivity(new Intent(this, DiscoveryActivity.class));
}
}
@Override @Override
protected void onDestroy() { protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
@ -346,12 +383,52 @@ public class ControlCenter extends Activity {
} }
} }
if (deviceList.isEmpty()) {
background.setVisibility(View.VISIBLE);
} else {
background.setVisibility(View.INVISIBLE);
}
if (connected) { if (connected) {
hintTextView.setText(R.string.tap_connected_device_for_app_mananger); DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(selectedDevice);
hintTextView.setText(coordinator.getTapString());
} else if (!deviceList.isEmpty()) { } else if (!deviceList.isEmpty()) {
hintTextView.setText(R.string.tap_a_device_to_connect); hintTextView.setText(R.string.tap_a_device_to_connect);
} }
mGBDeviceAdapter.notifyDataSetChanged(); mGBDeviceAdapter.notifyDataSetChanged();
} }
@TargetApi(Build.VERSION_CODES.M)
private void checkAndRequestPermissions() {
List<String> wantedPermissions = new ArrayList<>();
if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.BLUETOOTH);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.BLUETOOTH_ADMIN);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_CONTACTS);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.CALL_PHONE);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_PHONE_STATE);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.PROCESS_OUTGOING_CALLS) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_SMS);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.SEND_SMS);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_CALENDAR);
if (ContextCompat.checkSelfPermission(this, "com.fsck.k9.permission.READ_MESSAGES") == PackageManager.PERMISSION_DENIED)
wantedPermissions.add("com.fsck.k9.permission.READ_MESSAGES");
if (!wantedPermissions.isEmpty())
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[wantedPermissions.size()]), 0);
}
} }

View File

@ -1,6 +1,5 @@
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -14,6 +13,7 @@ import android.os.Bundle;
import android.support.v4.app.NavUtils; import android.support.v4.app.NavUtils;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput; import android.support.v4.app.RemoteInput;
import android.support.v4.content.LocalBroadcastManager;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
@ -29,14 +29,16 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
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.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class DebugActivity extends Activity { public class DebugActivity extends GBActivity {
private static final Logger LOG = LoggerFactory.getLogger(DebugActivity.class); private static final Logger LOG = LoggerFactory.getLogger(DebugActivity.class);
private static final String EXTRA_REPLY = "reply"; private static final String EXTRA_REPLY = "reply";
@ -53,6 +55,7 @@ public class DebugActivity extends Activity {
private Button setMusicInfoButton; private Button setMusicInfoButton;
private Button setTimeButton; private Button setTimeButton;
private Button rebootButton; private Button rebootButton;
private Button HeartRateButton;
private Button exportDBButton; private Button exportDBButton;
private Button importDBButton; private Button importDBButton;
private Button deleteDBButton; private Button deleteDBButton;
@ -62,15 +65,22 @@ public class DebugActivity extends Activity {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) { switch (intent.getAction()) {
case GBApplication.ACTION_QUIT: case GBApplication.ACTION_QUIT: {
finish(); finish();
break; break;
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);
LOG.info("got wearable reply: " + reply); LOG.info("got wearable reply: " + reply);
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: {
int hrValue = intent.getIntExtra(DeviceService.EXTRA_HEART_RATE_VALUE, -1);
GB.toast(DebugActivity.this, "Heart Rate measured: " + hrValue, Toast.LENGTH_LONG, GB.INFO);
break;
}
} }
} }
}; };
@ -79,12 +89,13 @@ public class DebugActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_debug); setContentView(R.layout.activity_debug);
getActionBar().setDisplayHomeAsUpEnabled(true);
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(GBApplication.ACTION_QUIT); filter.addAction(GBApplication.ACTION_QUIT);
filter.addAction(ACTION_REPLY); filter.addAction(ACTION_REPLY);
registerReceiver(mReceiver, filter); filter.addAction(DeviceService.ACTION_HEARTRATE_MEASUREMENT);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
registerReceiver(mReceiver, filter); // for ACTION_REPLY
editContent = (EditText) findViewById(R.id.editContent); editContent = (EditText) findViewById(R.id.editContent);
sendSMSButton = (Button) findViewById(R.id.sendSMSButton); sendSMSButton = (Button) findViewById(R.id.sendSMSButton);
@ -117,20 +128,20 @@ public class DebugActivity extends Activity {
incomingCallButton.setOnClickListener(new View.OnClickListener() { incomingCallButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
GBApplication.deviceService().onSetCallState( CallSpec callSpec = new CallSpec();
editContent.getText().toString(), callSpec.command = CallSpec.CALL_INCOMING;
null, callSpec.number = editContent.getText().toString();
ServiceCommand.CALL_INCOMING); GBApplication.deviceService().onSetCallState(callSpec);
} }
}); });
outgoingCallButton = (Button) findViewById(R.id.outgoingCallButton); outgoingCallButton = (Button) 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) {
GBApplication.deviceService().onSetCallState( CallSpec callSpec = new CallSpec();
editContent.getText().toString(), callSpec.command = CallSpec.CALL_OUTGOING;
null, callSpec.number = editContent.getText().toString();
ServiceCommand.CALL_OUTGOING); GBApplication.deviceService().onSetCallState(callSpec);
} }
}); });
@ -138,20 +149,18 @@ public class DebugActivity extends Activity {
startCallButton.setOnClickListener(new View.OnClickListener() { startCallButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
GBApplication.deviceService().onSetCallState( CallSpec callSpec = new CallSpec();
null, callSpec.command = CallSpec.CALL_START;
null, GBApplication.deviceService().onSetCallState(callSpec);
ServiceCommand.CALL_START);
} }
}); });
endCallButton = (Button) findViewById(R.id.endCallButton); endCallButton = (Button) 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) {
GBApplication.deviceService().onSetCallState( CallSpec callSpec = new CallSpec();
null, callSpec.command = CallSpec.CALL_END;
null, GBApplication.deviceService().onSetCallState(callSpec);
ServiceCommand.CALL_END);
} }
}); });
@ -185,15 +194,28 @@ public class DebugActivity extends Activity {
GBApplication.deviceService().onReboot(); GBApplication.deviceService().onReboot();
} }
}); });
HeartRateButton = (Button) findViewById(R.id.HearRateButton);
HeartRateButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GB.toast("Measuring heart rate, please wait...", Toast.LENGTH_LONG, GB.INFO);
GBApplication.deviceService().onHeartRateTest();
}
});
setMusicInfoButton = (Button) findViewById(R.id.setMusicInfoButton); setMusicInfoButton = (Button) 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) {
GBApplication.deviceService().onSetMusicInfo( MusicSpec musicSpec = new MusicSpec();
editContent.getText().toString() + "(artist)", musicSpec.artist = editContent.getText().toString() + "(artist)";
editContent.getText().toString() + "(album)", musicSpec.album = editContent.getText().toString() + "(album)";
editContent.getText().toString() + "(tracl)"); musicSpec.track = editContent.getText().toString() + "(track)";
musicSpec.duration = 10;
musicSpec.trackCount = 5;
musicSpec.trackNr = 2;
GBApplication.deviceService().onSetMusicInfo(musicSpec);
} }
}); });
@ -337,6 +359,7 @@ public class DebugActivity extends Activity {
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
unregisterReceiver(mReceiver); unregisterReceiver(mReceiver);
} }

View File

@ -32,7 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class DiscoveryActivity extends Activity implements AdapterView.OnItemClickListener { public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemClickListener {
private static final Logger LOG = LoggerFactory.getLogger(DiscoveryActivity.class); private static final Logger LOG = LoggerFactory.getLogger(DiscoveryActivity.class);
private static final long SCAN_DURATION = 60000; // 60s private static final long SCAN_DURATION = 60000; // 60s
@ -249,7 +249,13 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
private void bluetoothStateChanged(int oldState, int newState) { private void bluetoothStateChanged(int oldState, int newState) {
discoveryFinished(); discoveryFinished();
startButton.setEnabled(newState == BluetoothAdapter.STATE_ON); if (newState == BluetoothAdapter.STATE_ON) {
this.adapter = BluetoothAdapter.getDefaultAdapter();
startButton.setEnabled(true);
} else {
this.adapter = null;
startButton.setEnabled(false);
}
} }
private void discoveryFinished() { private void discoveryFinished() {
@ -284,8 +290,15 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
return false; return false;
} }
BluetoothAdapter adapter = bluetoothService.getAdapter(); BluetoothAdapter adapter = bluetoothService.getAdapter();
if (adapter == null) {
LOG.warn("No bluetooth available");
this.adapter = null;
return false;
}
if (!adapter.isEnabled()) { if (!adapter.isEnabled()) {
LOG.warn("Bluetooth not enabled"); LOG.warn("Bluetooth not enabled");
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(enableBtIntent);
this.adapter = null; this.adapter = null;
return false; return false;
} }

View File

@ -0,0 +1,216 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.util.Log;
import android.view.MenuItem;
import android.webkit.ConsoleMessage;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
public class ExternalPebbleJSActivity extends GBActivity {
private static final Logger LOG = LoggerFactory.getLogger(ExternalPebbleJSActivity.class);
private UUID appUuid;
private GBDevice mGBDevice = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(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");
}
String queryString = "";
Uri uri = getIntent().getData();
if (uri != null) {
//getting back with configuration data
appUuid = UUID.fromString(uri.getHost());
queryString = uri.getEncodedQuery();
} else {
appUuid = (UUID) getIntent().getSerializableExtra("app_uuid");
}
setContentView(R.layout.activity_external_pebble_js);
WebView myWebView = (WebView) findViewById(R.id.configureWebview);
myWebView.clearCache(true);
myWebView.setWebViewClient(new GBWebClient());
myWebView.setWebChromeClient(new GBChromeClient());
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
//needed to access the DOM
webSettings.setDomStorageEnabled(true);
JSInterface gbJSInterface = new JSInterface();
myWebView.addJavascriptInterface(gbJSInterface, "GBjs");
myWebView.loadUrl("file:///android_asset/app_config/configure.html?" + queryString);
}
private JSONObject getAppConfigurationKeys() {
try {
File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
File configurationFile = new File(destDir, appUuid.toString() + ".json");
if (configurationFile.exists()) {
String jsonstring = FileUtils.getStringFromFile(configurationFile);
JSONObject json = new JSONObject(jsonstring);
return json.getJSONObject("appKeys");
}
} catch (IOException | JSONException e) {
e.printStackTrace();
}
return null;
}
private class GBChromeClient extends WebChromeClient {
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
if (ConsoleMessage.MessageLevel.ERROR.equals(consoleMessage.messageLevel())) {
GB.toast(consoleMessage.message(), Toast.LENGTH_LONG, GB.ERROR);
//TODO: show error page
}
return super.onConsoleMessage(consoleMessage);
}
}
private class GBWebClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("http://") || url.startsWith("https://")) {
Intent i = new Intent(Intent.ACTION_VIEW,
Uri.parse(url));
startActivity(i);
} else {
url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json=");
view.loadUrl(url);
}
return true;
}
}
private class JSInterface {
public JSInterface() {
}
@JavascriptInterface
public void gbLog(String msg) {
Log.d("WEBVIEW", msg);
}
@JavascriptInterface
public void sendAppMessage(String msg) {
LOG.debug("from WEBVIEW: ", msg);
JSONObject knownKeys = getAppConfigurationKeys();
try {
JSONObject in = new JSONObject(msg);
JSONObject out = new JSONObject();
String cur_key;
for (Iterator<String> key = in.keys(); key.hasNext(); ) {
cur_key = key.next();
int pebbleAppIndex = knownKeys.optInt(cur_key);
if (pebbleAppIndex != 0) {
Object obj = in.get(cur_key);
if (obj instanceof Boolean) {
obj = ((Boolean) obj) ? "true" : "false";
}
out.put(String.valueOf(pebbleAppIndex), obj);
} else {
GB.toast("Discarded key " + cur_key + ", not found in the local configuration.", Toast.LENGTH_SHORT, GB.WARN);
}
}
LOG.info(out.toString());
GBApplication.deviceService().onAppConfiguration(appUuid, out.toString());
} catch (JSONException e) {
e.printStackTrace();
}
}
@JavascriptInterface
public String getActiveWatchInfo() {
JSONObject wi = new JSONObject();
try {
wi.put("firmware", mGBDevice.getFirmwareVersion());
wi.put("platform", PebbleUtils.getPlatformName(mGBDevice.getHardwareVersion()));
wi.put("model", PebbleUtils.getModel(mGBDevice.getHardwareVersion()));
//TODO: use real info
wi.put("language", "en");
} catch (JSONException e) {
e.printStackTrace();
}
//Json not supported apparently, we need to cast back and forth
return wi.toString();
}
@JavascriptInterface
public String getAppConfigurationFile() {
try {
File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
File configurationFile = new File(destDir, appUuid.toString() + "_config.js");
if (configurationFile.exists()) {
return "file:///" + configurationFile.getAbsolutePath();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@JavascriptInterface
public String getAppUUID() {
return appUuid.toString();
}
@JavascriptInterface
public String getWatchToken() {
//specification says: A string that is is guaranteed to be identical for each Pebble device for the same app across different mobile devices. The token is unique to your app and cannot be used to track Pebble devices across applications. see https://developer.pebble.com/docs/js/Pebble/
return "gb" + appUuid.toString();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -1,6 +1,5 @@
package nodomain.freeyourgadget.gadgetbridge.activities; package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.Activity;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -29,14 +28,16 @@ import nodomain.freeyourgadget.gadgetbridge.adapter.ItemWithDetailsAdapter;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails; import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class FwAppInstallerActivity extends Activity implements InstallActivity { public class FwAppInstallerActivity extends GBActivity implements InstallActivity {
private static final Logger LOG = LoggerFactory.getLogger(FwAppInstallerActivity.class); private static final Logger LOG = LoggerFactory.getLogger(FwAppInstallerActivity.class);
private static final String ITEM_DETAILS = "details";
private TextView fwAppInstallTextView; private TextView fwAppInstallTextView;
private Button installButton; private Button installButton;
@ -45,13 +46,22 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
private InstallHandler installHandler; private InstallHandler installHandler;
private boolean mayConnect; private boolean mayConnect;
private ProgressBar mProgressBar;
private ListView itemListView;
private final List<ItemWithDetails> mItems = new ArrayList<>();
private ItemWithDetailsAdapter mItemAdapter;
private ListView detailsListView;
private ItemWithDetailsAdapter mDetailsItemAdapter;
private ArrayList<ItemWithDetails> mDetails = new ArrayList<>();
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
if (action.equals(GBApplication.ACTION_QUIT)) { if (GBApplication.ACTION_QUIT.equals(action)) {
finish(); finish();
} else if (action.equals(GBDevice.ACTION_DEVICE_CHANGED)) { } else if (GBDevice.ACTION_DEVICE_CHANGED.equals(action)) {
device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE); device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
if (device != null) { if (device != null) {
refreshBusyState(device); refreshBusyState(device);
@ -67,13 +77,13 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
validateInstallation(); validateInstallation();
} }
} }
} else if (GB.ACTION_DISPLAY_MESSAGE.equals(action)) {
String message = intent.getStringExtra(GB.DISPLAY_MESSAGE_MESSAGE);
int severity = intent.getIntExtra(GB.DISPLAY_MESSAGE_SEVERITY, GB.INFO);
addMessage(message, severity);
} }
} }
}; };
private ProgressBar mProgressBar;
private ListView itemListView;
private final List<ItemWithDetails> mItems = new ArrayList<>();
private ItemWithDetailsAdapter mItemAdapter;
private void refreshBusyState(GBDevice dev) { private void refreshBusyState(GBDevice dev) {
if (dev.isConnecting() || dev.isBusy()) { if (dev.isConnecting() || dev.isBusy()) {
@ -102,11 +112,18 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_appinstaller); setContentView(R.layout.activity_appinstaller);
getActionBar().setDisplayHomeAsUpEnabled(true);
GBDevice dev = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE); GBDevice dev = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
if (dev != null) { if (dev != null) {
device = dev; device = dev;
} }
if (savedInstanceState != null) {
mDetails = savedInstanceState.getParcelableArrayList(ITEM_DETAILS);
if (mDetails == null) {
mDetails = new ArrayList<>();
}
}
mayConnect = true; mayConnect = true;
itemListView = (ListView) findViewById(R.id.itemListView); itemListView = (ListView) findViewById(R.id.itemListView);
mItemAdapter = new ItemWithDetailsAdapter(this, mItems); mItemAdapter = new ItemWithDetailsAdapter(this, mItems);
@ -114,10 +131,15 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
fwAppInstallTextView = (TextView) findViewById(R.id.infoTextView); fwAppInstallTextView = (TextView) findViewById(R.id.infoTextView);
installButton = (Button) findViewById(R.id.installButton); installButton = (Button) findViewById(R.id.installButton);
mProgressBar = (ProgressBar) findViewById(R.id.installProgressBar); mProgressBar = (ProgressBar) findViewById(R.id.installProgressBar);
detailsListView = (ListView) findViewById(R.id.detailsListView);
mDetailsItemAdapter = new ItemWithDetailsAdapter(this, mDetails);
mDetailsItemAdapter.setSize(ItemWithDetailsAdapter.SIZE_SMALL);
detailsListView.setAdapter(mDetailsItemAdapter);
setInstallEnabled(false); setInstallEnabled(false);
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(GBApplication.ACTION_QUIT); filter.addAction(GBApplication.ACTION_QUIT);
filter.addAction(GBDevice.ACTION_DEVICE_CHANGED); filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
filter.addAction(GB.ACTION_DISPLAY_MESSAGE);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
installButton.setOnClickListener(new View.OnClickListener() { installButton.setOnClickListener(new View.OnClickListener() {
@ -145,6 +167,12 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
} }
} }
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList(ITEM_DETAILS, mDetails);
}
private InstallHandler findInstallHandlerFor(Uri uri) { private InstallHandler findInstallHandlerFor(Uri uri) {
for (DeviceCoordinator coordinator : DeviceHelper.getInstance().getAllCoordinators()) { for (DeviceCoordinator coordinator : DeviceHelper.getInstance().getAllCoordinators()) {
InstallHandler handler = coordinator.findInstallHandler(uri, this); InstallHandler handler = coordinator.findInstallHandler(uri, this);
@ -195,4 +223,9 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
mItems.add(item); mItems.add(item);
mItemAdapter.notifyDataSetChanged(); mItemAdapter.notifyDataSetChanged();
} }
private void addMessage(String message, int severity) {
mDetails.add(new GenericItem(message));
mDetailsItemAdapter.notifyDataSetChanged();
}
} }

View File

@ -0,0 +1,22 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
public class GBActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
if (GBApplication.isDarkThemeEnabled()) {
setTheme(R.style.GadgetbridgeThemeDark);
} else {
setTheme(R.style.GadgetbridgeTheme);
}
super.onCreate(savedInstanceState);
}
}

View File

@ -0,0 +1,14 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
public class HeartRateUtils {
public static final int MAX_HEART_RATE_VALUE = 250;
public static final int MIN_HEART_RATE_VALUE = 0;
/**
* The maxiumum gap between two hr measurements in which
* we interpolate between the measurements. Otherwise, two
* distinct measurements will be shown.
*
* Value is in minutes
*/
public static final int MAX_HR_MEASUREMENTS_GAP_MINUTES = 10;
}

View File

@ -7,11 +7,22 @@ import android.os.Bundle;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast;
import java.io.IOException;
import java.util.List; import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GENDER;
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_WEIGHT_KG;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_YEAR_OF_BIRTH;
public class SettingsActivity extends AbstractSettingsActivity { public class SettingsActivity extends AbstractSettingsActivity {
@Override @Override
@ -74,6 +85,28 @@ public class SettingsActivity extends AbstractSettingsActivity {
}); });
pref = findPreference("log_to_file");
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
boolean doEnable = Boolean.TRUE.equals(newVal);
try {
if (doEnable) {
FileUtils.getExternalFilesDir(); // ensures that it is created
}
GBApplication.setupLogging(doEnable);
} catch (IOException ex) {
GB.toast(getApplicationContext(),
getString(R.string.error_creating_directory_for_logfiles, ex.getLocalizedMessage()),
Toast.LENGTH_LONG,
GB.ERROR,
ex);
}
return true;
}
});
// 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);
@ -103,10 +136,6 @@ public class SettingsActivity extends AbstractSettingsActivity {
@Override @Override
protected String[] getPreferenceKeysWithSummary() { protected String[] getPreferenceKeysWithSummary() {
return new String[]{ return new String[]{
"audio_player",
"notification_mode_calls",
"notification_mode_sms",
"notification_mode_k9mail",
"pebble_emu_addr", "pebble_emu_addr",
"pebble_emu_port", "pebble_emu_port",
"pebble_reconnect_attempts", "pebble_reconnect_attempts",
@ -127,6 +156,10 @@ public class SettingsActivity extends AbstractSettingsActivity {
"canned_reply_14", "canned_reply_14",
"canned_reply_15", "canned_reply_15",
"canned_reply_16", "canned_reply_16",
PREF_USER_YEAR_OF_BIRTH,
PREF_USER_HEIGHT_CM,
PREF_USER_WEIGHT_KG,
PREF_USER_SLEEP_DURATION,
}; };
} }

View File

@ -4,6 +4,7 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
@ -12,9 +13,15 @@ import android.view.View;
import com.github.mikephil.charting.charts.BarLineChartBase; import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.Chart; import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData; import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet; import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry; import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.CombinedData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -23,14 +30,17 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBAccess; import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
@ -72,6 +82,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
} }
}; };
private boolean mChartDirty = true; private boolean mChartDirty = true;
private boolean supportsHeartrateChart = true;
private AsyncTask refreshTask;
public boolean isChartDirty() { public boolean isChartDirty() {
return mChartDirty; return mChartDirty;
@ -79,6 +91,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
public abstract String getTitle(); public abstract String getTitle();
public boolean supportsHeartrate() {
return supportsHeartrateChart;
}
protected static final class ActivityConfig { protected static final class ActivityConfig {
public final int type; public final int type;
public final String label; public final String label;
@ -101,11 +117,15 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected int DESCRIPTION_COLOR; protected int DESCRIPTION_COLOR;
protected int CHART_TEXT_COLOR; protected int CHART_TEXT_COLOR;
protected int LEGEND_TEXT_COLOR; protected int LEGEND_TEXT_COLOR;
protected int HEARTRATE_COLOR;
protected int HEARTRATE_FILL_COLOR;
protected int AK_ACTIVITY_COLOR; protected int AK_ACTIVITY_COLOR;
protected int AK_DEEP_SLEEP_COLOR; protected int AK_DEEP_SLEEP_COLOR;
protected int AK_LIGHT_SLEEP_COLOR; protected int AK_LIGHT_SLEEP_COLOR;
protected int AK_NOT_WORN_COLOR; protected int AK_NOT_WORN_COLOR;
protected String HEARTRATE_LABEL;
protected AbstractChartFragment(String... intentFilterActions) { protected AbstractChartFragment(String... intentFilterActions) {
mIntentFilterActions = new HashSet<>(); mIntentFilterActions = new HashSet<>();
if (intentFilterActions != null) { if (intentFilterActions != null) {
@ -130,15 +150,18 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
} }
protected void init() { protected void init() {
BACKGROUND_COLOR = getResources().getColor(R.color.background_material_light); BACKGROUND_COLOR = GBApplication.getBackgroundColor(getContext());
DESCRIPTION_COLOR = getResources().getColor(R.color.primarytext); LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(getContext());
CHART_TEXT_COLOR = getResources().getColor(R.color.secondarytext); CHART_TEXT_COLOR = getResources().getColor(R.color.secondarytext);
LEGEND_TEXT_COLOR = getResources().getColor(R.color.primarytext); HEARTRATE_COLOR = getResources().getColor(R.color.chart_heartrate);
HEARTRATE_FILL_COLOR = getResources().getColor(R.color.chart_heartrate_fill);
AK_ACTIVITY_COLOR = getResources().getColor(R.color.chart_activity_light); AK_ACTIVITY_COLOR = getResources().getColor(R.color.chart_activity_light);
AK_DEEP_SLEEP_COLOR = getResources().getColor(R.color.chart_light_sleep_light); AK_DEEP_SLEEP_COLOR = getResources().getColor(R.color.chart_light_sleep_light);
AK_LIGHT_SLEEP_COLOR = getResources().getColor(R.color.chart_deep_sleep_light); AK_LIGHT_SLEEP_COLOR = getResources().getColor(R.color.chart_deep_sleep_light);
AK_NOT_WORN_COLOR = getResources().getColor(R.color.chart_not_worn_light); AK_NOT_WORN_COLOR = getResources().getColor(R.color.chart_not_worn_light);
HEARTRATE_LABEL = getContext().getString(R.string.charts_legend_heartrate);
akActivity = new ActivityConfig(ActivityKind.TYPE_ACTIVITY, getString(R.string.abstract_chart_fragment_kind_activity), AK_ACTIVITY_COLOR); akActivity = new ActivityConfig(ActivityKind.TYPE_ACTIVITY, getString(R.string.abstract_chart_fragment_kind_activity), AK_ACTIVITY_COLOR);
akLightSleep = new ActivityConfig(ActivityKind.TYPE_LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR); akLightSleep = new ActivityConfig(ActivityKind.TYPE_LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR);
akDeepSleep = new ActivityConfig(ActivityKind.TYPE_DEEP_SLEEP, getString(R.string.abstract_chart_fragment_kind_deep_sleep), AK_DEEP_SLEEP_COLOR); akDeepSleep = new ActivityConfig(ActivityKind.TYPE_DEEP_SLEEP, getString(R.string.abstract_chart_fragment_kind_deep_sleep), AK_DEEP_SLEEP_COLOR);
@ -153,7 +176,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected ChartsHost getChartsHost() { protected ChartsHost getChartsHost() {
return (ChartsHost) getActivity(); return (ChartsHost) getActivity();
} }
private void setEndDate(Date date) { private void setEndDate(Date date) {
getChartsHost().setEndDate(date); getChartsHost().setEndDate(date);
} }
@ -315,6 +338,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
} }
protected void configureChartDefaults(Chart<?> chart) { protected void configureChartDefaults(Chart<?> chart) {
chart.setDescription("");
// if enabled, the chart will always start at zero on the y-axis // if enabled, the chart will always start at zero on the y-axis
chart.setNoDataText(getString(R.string.chart_no_data_synchronize)); chart.setNoDataText(getString(R.string.chart_no_data_synchronize));
@ -323,6 +348,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
// enable touch gestures // enable touch gestures
chart.setTouchEnabled(true); chart.setTouchEnabled(true);
setupLegend(chart);
} }
protected void configureBarLineChartDefaults(BarLineChartBase<?> chart) { protected void configureBarLineChartDefaults(BarLineChartBase<?> chart) {
@ -349,7 +376,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
if (chartsHost.getDevice() != null) { if (chartsHost.getDevice() != null) {
mChartDirty = false; mChartDirty = false;
updateDateInfo(getStartDate(), getEndDate()); updateDateInfo(getStartDate(), getEndDate());
createRefreshTask("Visualizing data", getActivity()).execute(); if (refreshTask != null && refreshTask.getStatus() != AsyncTask.Status.FINISHED) {
refreshTask.cancel(true);
}
refreshTask = createRefreshTask("Visualizing data", getActivity()).execute();
} }
} }
} }
@ -357,9 +387,9 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
/** /**
* This method reads the data from the database, analyzes and prepares it for * This method reads the data from the database, analyzes and prepares it for
* the charts. This will be called from a background task, so there must not be * the charts. This will be called from a background task, so there must not be
* any UI access. #renderCharts will be automatically called after this method. * any UI access. #updateChartsInUIThread and #renderCharts will be automatically called after this method.
*/ */
protected abstract void refreshInBackground(DBHandler db, GBDevice device); protected abstract ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device);
/** /**
* Triggers the actual (re-) rendering of the chart. * Triggers the actual (re-) rendering of the chart.
@ -367,7 +397,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
*/ */
protected abstract void renderCharts(); protected abstract void renderCharts();
protected void refresh(GBDevice gbDevice, BarLineChartBase chart, List<ActivitySample> samples) { protected DefaultChartsData refresh(GBDevice gbDevice, List<ActivitySample> samples) {
Calendar cal = GregorianCalendar.getInstance(); Calendar cal = GregorianCalendar.getInstance();
cal.clear(); cal.clear();
Date date; Date date;
@ -375,11 +405,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
String dateStringTo = ""; String dateStringTo = "";
LOG.info("" + getTitle() + ": number of samples:" + samples.size()); LOG.info("" + getTitle() + ": number of samples:" + samples.size());
CombinedData combinedData;
if (samples.size() > 1) { if (samples.size() > 1) {
float movement_divisor;
boolean annotate = true; boolean annotate = true;
boolean use_steps_as_movement; boolean use_steps_as_movement;
SampleProvider provider = getProvider(gbDevice);
int last_type = ActivityKind.TYPE_UNKNOWN; int last_type = ActivityKind.TYPE_UNKNOWN;
@ -389,7 +418,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
int numEntries = samples.size(); int numEntries = samples.size();
List<String> xLabels = new ArrayList<>(numEntries); List<String> xLabels = new ArrayList<>(numEntries);
List<BarEntry> activityEntries = new ArrayList<>(numEntries); List<BarEntry> activityEntries = new ArrayList<>(numEntries);
boolean hr = supportsHeartrate();
List<Entry> heartrateEntries = hr ? new ArrayList<Entry>(numEntries) : null;
List<Integer> colors = new ArrayList<>(numEntries); // this is kinda inefficient... List<Integer> colors = new ArrayList<>(numEntries); // this is kinda inefficient...
int lastHrSampleIndex = -1;
for (int i = 0; i < numEntries; i++) { for (int i = 0; i < numEntries; i++) {
ActivitySample sample = samples.get(i); ActivitySample sample = samples.get(i);
@ -431,6 +463,15 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
colors.add(akActivity.color); colors.add(akActivity.color);
} }
activityEntries.add(createBarEntry(value, i)); activityEntries.add(createBarEntry(value, i));
if (hr && isValidHeartRateValue(sample.getCustomValue())) {
if (lastHrSampleIndex > -1 && i - lastHrSampleIndex > HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
heartrateEntries.add(createLineEntry(0, lastHrSampleIndex + 1));
heartrateEntries.add(createLineEntry(0, i - 1));
}
heartrateEntries.add(createLineEntry(sample.getCustomValue(), i));
lastHrSampleIndex = i;
}
String xLabel = ""; String xLabel = "";
if (annotate) { if (annotate) {
@ -460,25 +501,34 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
xLabels.add(xLabel); xLabels.add(xLabel);
} }
chart.getXAxis().setValues(xLabels); // chart.getXAxis().setValues(xLabels);
BarDataSet activitySet = createActivitySet(activityEntries, colors, "Activity"); BarDataSet activitySet = createActivitySet(activityEntries, colors, "Activity");
ArrayList<BarDataSet> dataSets = new ArrayList<>();
dataSets.add(activitySet);
// create a data object with the datasets // create a data object with the datasets
BarData data = new BarData(xLabels, dataSets); combinedData = new CombinedData(xLabels);
data.setGroupSpace(0); List<IBarDataSet> list = new ArrayList<>();
list.add(activitySet);
BarData barData = new BarData(xLabels, list);
barData.setGroupSpace(0);
combinedData.setData(barData);
if (hr && heartrateEntries.size() > 0) {
LineDataSet heartrateSet = createHeartrateSet(heartrateEntries, "Heart Rate");
LineData lineData = new LineData(xLabels, heartrateSet);
combinedData.setData(lineData);
}
chart.setDescription("");
// chart.setDescription(getString(R.string.sleep_activity_date_range, dateStringFrom, dateStringTo)); // chart.setDescription(getString(R.string.sleep_activity_date_range, dateStringFrom, dateStringTo));
// chart.setDescriptionPosition(?, ?); // chart.setDescriptionPosition(?, ?);
} else {
setupLegend(chart); combinedData = new CombinedData(Collections.<String>emptyList());
chart.setData(data);
} }
return new DefaultChartsData(combinedData);
}
protected boolean isValidHeartRateValue(int value) {
return value > HeartRateUtils.MIN_HEART_RATE_VALUE && value < HeartRateUtils.MAX_HEART_RATE_VALUE;
} }
/** /**
@ -498,6 +548,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
return new BarEntry(value, index); return new BarEntry(value, index);
} }
protected Entry createLineEntry(float value, int index) {
return new Entry(value, index);
}
protected BarDataSet createActivitySet(List<BarEntry> values, List<Integer> colors, String label) { protected BarDataSet createActivitySet(List<BarEntry> values, List<Integer> colors, String label) {
BarDataSet set1 = new BarDataSet(values, label); BarDataSet set1 = new BarDataSet(values, label);
set1.setColors(colors); set1.setColors(colors);
@ -512,6 +566,27 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
// set1.setHighLightColor(Color.rgb(128, 0, 255)); // set1.setHighLightColor(Color.rgb(128, 0, 255));
// set1.setColor(Color.rgb(89, 178, 44)); // set1.setColor(Color.rgb(89, 178, 44));
set1.setValueTextColor(CHART_TEXT_COLOR); set1.setValueTextColor(CHART_TEXT_COLOR);
set1.setAxisDependency(YAxis.AxisDependency.LEFT);
return set1;
}
protected LineDataSet createHeartrateSet(List<Entry> values, String label) {
LineDataSet set1 = new LineDataSet(values, label);
set1.setLineWidth(0.8f);
set1.setColor(HEARTRATE_COLOR);
set1.setDrawCubic(true);
set1.setCubicIntensity(0.1f);
set1.setDrawCircles(false);
// set1.setCircleRadius(2f);
// set1.setDrawFilled(true);
// set1.setColor(getResources().getColor(android.R.color.background_light));
// set1.setCircleColor(HEARTRATE_COLOR);
// set1.setFillColor(ColorTemplate.getHoloBlue());
// set1.setHighLightColor(Color.rgb(128, 0, 255));
// set1.setColor(Color.rgb(89, 178, 44));
set1.setDrawValues(true);
set1.setValueTextColor(CHART_TEXT_COLOR);
set1.setAxisDependency(YAxis.AxisDependency.RIGHT);
return set1; return set1;
} }
@ -554,6 +629,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
} }
public class RefreshTask extends DBAccess { public class RefreshTask extends DBAccess {
private ChartsData chartsData;
public RefreshTask(String task, Context context) { public RefreshTask(String task, Context context) {
super(task, context); super(task, context);
} }
@ -562,7 +639,9 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected void doInBackground(DBHandler db) { protected void doInBackground(DBHandler db) {
ChartsHost chartsHost = getChartsHost(); ChartsHost chartsHost = getChartsHost();
if (chartsHost != null) { if (chartsHost != null) {
refreshInBackground(db, chartsHost.getDevice()); chartsData = refreshInBackground(chartsHost, db, chartsHost.getDevice());
} else {
cancel(true);
} }
} }
@ -571,6 +650,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
super.onPostExecute(o); super.onPostExecute(o);
FragmentActivity activity = getActivity(); FragmentActivity activity = getActivity();
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) { if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
updateChartsnUIThread(chartsData);
renderCharts(); renderCharts();
} else { } else {
LOG.info("Not rendering charts because activity is not available anymore"); LOG.info("Not rendering charts because activity is not available anymore");
@ -578,6 +658,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
} }
} }
protected abstract void updateChartsnUIThread(ChartsData chartsData);
/** /**
* Returns true if the date was successfully shifted, and false if the shift * Returns true if the date was successfully shifted, and false if the shift
* was ignored, e.g. when the to-value is in the future. * was ignored, e.g. when the to-value is in the future.
@ -621,4 +703,16 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
private int toTimestamp(Date date) { private int toTimestamp(Date date) {
return (int) ((date.getTime() / 1000)); return (int) ((date.getTime() / 1000));
} }
public static class DefaultChartsData extends ChartsData {
private final CombinedData combinedData;
public DefaultChartsData(CombinedData combinedData) {
this.combinedData = combinedData;
}
public CombinedData getCombinedData() {
return combinedData;
}
}
} }

View File

@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
@ -70,6 +71,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
// y.setDrawLabels(false); // y.setDrawLabels(false);
// TODO: make fixed max value optional // TODO: make fixed max value optional
y.setAxisMaxValue(1f); y.setAxisMaxValue(1f);
y.setAxisMinValue(0);
y.setDrawTopYLabelEntry(false); y.setDrawTopYLabelEntry(false);
y.setTextColor(CHART_TEXT_COLOR); y.setTextColor(CHART_TEXT_COLOR);
@ -78,10 +80,12 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
YAxis yAxisRight = mChart.getAxisRight(); YAxis yAxisRight = mChart.getAxisRight();
yAxisRight.setDrawGridLines(false); yAxisRight.setDrawGridLines(false);
yAxisRight.setEnabled(false); yAxisRight.setEnabled(supportsHeartrate());
yAxisRight.setDrawLabels(false); yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(false); yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR); yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE);
// refresh immediately instead of use refreshIfVisible(), for perceived performance // refresh immediately instead of use refreshIfVisible(), for perceived performance
refresh(); refresh();
@ -103,11 +107,16 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
} }
@Override @Override
protected void refreshInBackground(DBHandler db, GBDevice device) { protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
List<ActivitySample> samples = getSamples(db, device); List<ActivitySample> samples = getSamples(db, device);
refresh(device, mChart, samples); return refresh(device, samples);
}
@Override
protected void updateChartsnUIThread(ChartsData chartsData) {
DefaultChartsData dcd = (DefaultChartsData) chartsData;
mChart.getLegend().setTextColor(LEGEND_TEXT_COLOR); mChart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
mChart.setData(dcd.getCombinedData());
} }
protected void renderCharts() { protected void renderCharts() {
@ -125,6 +134,10 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
legendLabels.add(akDeepSleep.label); legendLabels.add(akDeepSleep.label);
legendColors.add(akNotWorn.color); legendColors.add(akNotWorn.color);
legendLabels.add(akNotWorn.label); legendLabels.add(akNotWorn.label);
if (supportsHeartrate()) {
legendColors.add(HEARTRATE_COLOR);
legendLabels.add(HEARTRATE_LABEL);
}
chart.getLegend().setCustom(legendColors, legendLabels); chart.getLegend().setCustom(legendColors, legendLabels);
} }

View File

@ -0,0 +1,4 @@
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
public abstract class ChartsData {
}

View File

@ -49,6 +49,7 @@ public class CustomBarChart extends BarChart {
/** /**
* Call this to set the next value for the Entry to be animated. * Call this to set the next value for the Entry to be animated.
* Call animateY() when ready to do that. * Call animateY() when ready to do that.
*
* @param nextValue * @param nextValue
*/ */
public void setSingleEntryYValue(float nextValue) { public void setSingleEntryYValue(float nextValue) {

View File

@ -23,6 +23,7 @@ import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData; import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet; import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry; import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet; import com.github.mikephil.charting.data.LineDataSet;
@ -39,10 +40,12 @@ import java.util.concurrent.TimeUnit;
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.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.Measurement;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class LiveActivityFragment extends AbstractChartFragment { public class LiveActivityFragment extends AbstractChartFragment {
@ -63,6 +66,9 @@ public class LiveActivityFragment extends AbstractChartFragment {
private final Steps mSteps = new Steps(); private final Steps mSteps = new Steps();
private ScheduledExecutorService pulseScheduler; private ScheduledExecutorService pulseScheduler;
private int maxStepsResetCounter; private int maxStepsResetCounter;
private List<Measurement> heartRateValues;
private LineDataSet mHeartRateSet;
private int mHeartRate;
private class Steps { private class Steps {
private int initialSteps; private int initialSteps;
@ -145,16 +151,36 @@ public class LiveActivityFragment extends AbstractChartFragment {
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 (action) {
case DeviceService.ACTION_REALTIME_STEPS: case DeviceService.ACTION_REALTIME_STEPS: {
int steps = intent.getIntExtra(DeviceService.EXTRA_REALTIME_STEPS, 0); int steps = intent.getIntExtra(DeviceService.EXTRA_REALTIME_STEPS, 0);
long timestamp = intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis()); long timestamp = intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
refreshCurrentSteps(steps, timestamp); addEntries(steps, timestamp);
break; break;
}
case DeviceService.ACTION_HEARTRATE_MEASUREMENT: {
int heartRate = intent.getIntExtra(DeviceService.EXTRA_HEART_RATE_VALUE, 0);
long timestamp = intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
if (isValidHeartRateValue(heartRate)) {
setCurrentHeartRate(heartRate, timestamp);
}
break;
}
} }
} }
}; };
private void refreshCurrentSteps(int steps, long timestamp) { private void setCurrentHeartRate(int heartRate, long timestamp) {
addHistoryDataSet(true);
mHeartRate = heartRate;
}
private int getCurrentHeartRate() {
int result = mHeartRate;
mHeartRate = -1;
return result;
}
private void addEntries(int steps, long timestamp) {
mSteps.updateCurrentSteps(steps, timestamp); mSteps.updateCurrentSteps(steps, timestamp);
if (++maxStepsResetCounter > RESET_COUNT) { if (++maxStepsResetCounter > RESET_COUNT) {
maxStepsResetCounter = 0; maxStepsResetCounter = 0;
@ -163,10 +189,10 @@ public class LiveActivityFragment extends AbstractChartFragment {
// Or: count down the steps until goal reached? And then flash GOAL REACHED -> Set stretch goal // Or: count down the steps until goal reached? And then flash GOAL REACHED -> Set stretch goal
LOG.info("Steps: " + steps + ", total: " + mSteps.getTotalSteps() + ", current: " + mSteps.getStepsPerMinute(false)); LOG.info("Steps: " + steps + ", total: " + mSteps.getTotalSteps() + ", current: " + mSteps.getStepsPerMinute(false));
// refreshCurrentSteps(); // addEntries();
} }
private void refreshCurrentSteps() { private void addEntries() {
mTotalStepsChart.setSingleEntryYValue(mSteps.getTotalSteps()); mTotalStepsChart.setSingleEntryYValue(mSteps.getTotalSteps());
YAxis stepsPerMinuteCurrentYAxis = mStepsPerMinuteCurrentChart.getAxisLeft(); YAxis stepsPerMinuteCurrentYAxis = mStepsPerMinuteCurrentChart.getAxisLeft();
int maxStepsPerMinute = mSteps.getMaxStepsPerMinute(); int maxStepsPerMinute = mSteps.getMaxStepsPerMinute();
@ -180,24 +206,36 @@ public class LiveActivityFragment extends AbstractChartFragment {
int stepsPerMinute = mSteps.getStepsPerMinute(true); int stepsPerMinute = mSteps.getStepsPerMinute(true);
mStepsPerMinuteCurrentChart.setSingleEntryYValue(stepsPerMinute); mStepsPerMinuteCurrentChart.setSingleEntryYValue(stepsPerMinute);
if (mStepsPerMinuteHistoryChart.getData() == null) { if (!addHistoryDataSet(false)) {
if (mSteps.getTotalSteps() == 0) { return;
return; // ignore the first default value to keep the "no-data-description" visible
}
LineData data = new LineData();
mStepsPerMinuteHistoryChart.setData(data);
data.addDataSet(mHistorySet);
} }
LineData historyData = (LineData) mStepsPerMinuteHistoryChart.getData(); ChartData data = mStepsPerMinuteHistoryChart.getData();
historyData.addXValue(""); data.addXValue("");
historyData.addEntry(new Entry(stepsPerMinute, mHistorySet.getEntryCount()), 0); if (stepsPerMinute < 0) {
stepsPerMinute = 0;
}
mHistorySet.addEntry(new Entry(stepsPerMinute, data.getXValCount() - 1));
int hr = getCurrentHeartRate();
if (hr < 0) {
hr = 0;
}
mHeartRateSet.addEntry(new Entry(hr, data.getXValCount() - 1));
}
mTotalStepsData.notifyDataSetChanged(); private boolean addHistoryDataSet(boolean force) {
mStepsPerMinuteData.notifyDataSetChanged(); if (mStepsPerMinuteHistoryChart.getData() == null) {
mStepsPerMinuteHistoryChart.notifyDataSetChanged(); // ignore the first default value to keep the "no-data-description" visible
if (force || mSteps.getTotalSteps() > 0) {
renderCharts(); LineData data = new LineData();
data.addDataSet(mHistorySet);
data.addDataSet(mHeartRateSet);
mStepsPerMinuteHistoryChart.setData(data);
return true;
}
return false;
}
return true;
} }
@Nullable @Nullable
@ -205,6 +243,8 @@ public class LiveActivityFragment extends AbstractChartFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
IntentFilter filterLocal = new IntentFilter(); IntentFilter filterLocal = new IntentFilter();
filterLocal.addAction(DeviceService.ACTION_REALTIME_STEPS); filterLocal.addAction(DeviceService.ACTION_REALTIME_STEPS);
filterLocal.addAction(DeviceService.ACTION_HEARTRATE_MEASUREMENT);
heartRateValues = new ArrayList<>();
View rootView = inflater.inflate(R.layout.fragment_live_activity, container, false); View rootView = inflater.inflate(R.layout.fragment_live_activity, container, false);
@ -227,16 +267,12 @@ public class LiveActivityFragment extends AbstractChartFragment {
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
if (pulseScheduler != null) { stopActivityPulse();
pulseScheduler.shutdownNow();
pulseScheduler = null;
}
} }
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
pulseScheduler = startActivityPulse();
} }
private ScheduledExecutorService startActivityPulse() { private ScheduledExecutorService startActivityPulse() {
@ -258,11 +294,33 @@ public class LiveActivityFragment extends AbstractChartFragment {
return service; return service;
} }
private void stopActivityPulse() {
if (pulseScheduler != null) {
pulseScheduler.shutdownNow();
pulseScheduler = null;
}
}
/** /**
* Called in the UI thread. * Called in the UI thread.
*/ */
private void pulse() { private void pulse() {
refreshCurrentSteps(); addEntries();
LineData historyData = (LineData) mStepsPerMinuteHistoryChart.getData();
if (historyData == null) {
return;
}
historyData.notifyDataChanged();
mTotalStepsData.notifyDataSetChanged();
mStepsPerMinuteData.notifyDataSetChanged();
mStepsPerMinuteHistoryChart.notifyDataSetChanged();
renderCharts();
// have to enable it again and again to keep it measureing
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(true);
} }
private long getPulseIntervalMillis() { private long getPulseIntervalMillis() {
@ -272,15 +330,19 @@ public class LiveActivityFragment extends AbstractChartFragment {
@Override @Override
protected void onMadeVisibleInActivity() { protected void onMadeVisibleInActivity() {
GBApplication.deviceService().onEnableRealtimeSteps(true); GBApplication.deviceService().onEnableRealtimeSteps(true);
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(true);
super.onMadeVisibleInActivity(); super.onMadeVisibleInActivity();
if (getActivity() != null) { if (getActivity() != null) {
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} }
pulseScheduler = startActivityPulse();
} }
@Override @Override
protected void onMadeInvisibleInActivity() { protected void onMadeInvisibleInActivity() {
stopActivityPulse();
GBApplication.deviceService().onEnableRealtimeSteps(false); GBApplication.deviceService().onEnableRealtimeSteps(false);
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(false);
if (getActivity() != null) { if (getActivity() != null) {
getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} }
@ -289,6 +351,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
@Override @Override
public void onDestroyView() { public void onDestroyView() {
onMadeInvisibleInActivity();
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mReceiver); LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mReceiver);
super.onDestroyView(); super.onDestroyView();
} }
@ -315,9 +378,9 @@ public class LiveActivityFragment extends AbstractChartFragment {
List<String> xLabels = new ArrayList<>(); List<String> xLabels = new ArrayList<>();
List<Integer> colors = new ArrayList<>(); List<Integer> colors = new ArrayList<>();
entries.add(new BarEntry(0,0)); entries.add(new BarEntry(0, 0));
entries.add(entry); entries.add(entry);
entries.add(new BarEntry(0,2)); entries.add(new BarEntry(0, 2));
colors.add(akActivity.color); colors.add(akActivity.color);
colors.add(akActivity.color); colors.add(akActivity.color);
colors.add(akActivity.color); colors.add(akActivity.color);
@ -346,6 +409,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
private void setupHistoryChart(BarLineChartBase chart) { private void setupHistoryChart(BarLineChartBase chart) {
configureBarLineChartDefaults(chart); configureBarLineChartDefaults(chart);
chart.setTouchEnabled(false); // no zooming or anything, because it's updated all the time
chart.setBackgroundColor(BACKGROUND_COLOR); chart.setBackgroundColor(BACKGROUND_COLOR);
chart.setDescriptionColor(DESCRIPTION_COLOR); chart.setDescriptionColor(DESCRIPTION_COLOR);
chart.setDescription(getString(R.string.live_activity_steps_per_minute_history)); chart.setDescription(getString(R.string.live_activity_steps_per_minute_history));
@ -367,22 +431,28 @@ public class LiveActivityFragment extends AbstractChartFragment {
y.setDrawGridLines(false); y.setDrawGridLines(false);
y.setDrawTopYLabelEntry(false); y.setDrawTopYLabelEntry(false);
y.setTextColor(CHART_TEXT_COLOR); y.setTextColor(CHART_TEXT_COLOR);
y.setEnabled(true); y.setEnabled(true);
y.setAxisMinValue(0);
YAxis yAxisRight = chart.getAxisRight(); YAxis yAxisRight = chart.getAxisRight();
yAxisRight.setDrawGridLines(false); yAxisRight.setDrawGridLines(false);
yAxisRight.setEnabled(false); yAxisRight.setEnabled(true);
yAxisRight.setDrawLabels(false); yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(false); yAxisRight.setDrawTopYLabelEntry(false);
yAxisRight.setTextColor(CHART_TEXT_COLOR); yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE);
mHistorySet = new LineDataSet(new ArrayList<Entry>(), getString(R.string.live_activity_steps_history)); mHistorySet = new LineDataSet(new ArrayList<Entry>(), getString(R.string.live_activity_steps_history));
mHistorySet.setAxisDependency(YAxis.AxisDependency.LEFT);
mHistorySet.setColor(akActivity.color); mHistorySet.setColor(akActivity.color);
mHistorySet.setDrawCircles(false); mHistorySet.setDrawCircles(false);
mHistorySet.setDrawCubic(true); mHistorySet.setDrawCubic(true);
mHistorySet.setDrawFilled(true); mHistorySet.setDrawFilled(true);
mHistorySet.setDrawValues(false); mHistorySet.setDrawValues(false);
mHeartRateSet = createHeartrateSet(new ArrayList<Entry>(), getString(R.string.live_activity_heart_rate));
mHeartRateSet.setDrawValues(false);
} }
@Override @Override
@ -402,7 +472,13 @@ public class LiveActivityFragment extends AbstractChartFragment {
} }
@Override @Override
protected void refreshInBackground(DBHandler db, GBDevice device) { protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
throw new UnsupportedOperationException();
}
@Override
protected void updateChartsnUIThread(ChartsData chartsData) {
throw new UnsupportedOperationException();
} }
@Override @Override

View File

@ -8,8 +8,8 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.github.mikephil.charting.animation.Easing; import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.Chart; import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.CombinedChart;
import com.github.mikephil.charting.charts.PieChart; import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.components.XAxis; import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis; import com.github.mikephil.charting.components.YAxis;
@ -27,6 +27,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount; import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
@ -39,7 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
public class SleepChartFragment extends AbstractChartFragment { public class SleepChartFragment extends AbstractChartFragment {
protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class); protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class);
private BarLineChartBase mActivityChart; private CombinedChart mActivityChart;
private PieChart mSleepAmountChart; private PieChart mSleepAmountChart;
private int mSmartAlarmFrom = -1; private int mSmartAlarmFrom = -1;
@ -48,14 +49,16 @@ public class SleepChartFragment extends AbstractChartFragment {
private int mSmartAlarmGoneOff = -1; private int mSmartAlarmGoneOff = -1;
@Override @Override
protected void refreshInBackground(DBHandler db, GBDevice device) { protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
List<ActivitySample> samples = getSamples(db, device); List<ActivitySample> samples = getSamples(db, device);
refresh(device, mActivityChart, samples); MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples);
refreshSleepAmounts(device, mSleepAmountChart, samples); DefaultChartsData chartsData = refresh(device, samples);
return new MyChartsData(mySleepChartsData, chartsData);
} }
private void refreshSleepAmounts(GBDevice mGBDevice, PieChart pieChart, List<ActivitySample> samples) { private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<ActivitySample> samples) {
ActivityAnalysis analysis = new ActivityAnalysis(); ActivityAnalysis analysis = new ActivityAnalysis();
ActivityAmounts amounts = analysis.calculateActivityAmounts(samples); ActivityAmounts amounts = analysis.calculateActivityAmounts(samples);
PieData data = new PieData(); PieData data = new PieData();
@ -73,7 +76,6 @@ public class SleepChartFragment extends AbstractChartFragment {
} }
} }
String totalSleep = DateTimeUtils.formatDurationHoursMinutes(totalSeconds, TimeUnit.SECONDS); String totalSleep = DateTimeUtils.formatDurationHoursMinutes(totalSeconds, TimeUnit.SECONDS);
pieChart.setCenterText(totalSleep);
PieDataSet set = new PieDataSet(entries, ""); PieDataSet set = new PieDataSet(entries, "");
set.setValueFormatter(new ValueFormatter() { set.setValueFormatter(new ValueFormatter() {
@Override @Override
@ -83,10 +85,18 @@ public class SleepChartFragment extends AbstractChartFragment {
}); });
set.setColors(colors); set.setColors(colors);
data.setDataSet(set); data.setDataSet(set);
pieChart.setData(data);
pieChart.getLegend().setEnabled(false);
//setupLegend(pieChart); //setupLegend(pieChart);
return new MySleepChartsData(totalSleep, data);
}
@Override
protected void updateChartsnUIThread(ChartsData chartsData) {
MyChartsData mcd = (MyChartsData) chartsData;
mSleepAmountChart.setCenterText(mcd.getPieData().getTotalSleep());
mSleepAmountChart.setData(mcd.getPieData().getPieData());
mActivityChart.setData(mcd.getChartsData().getCombinedData());
} }
@Override @Override
@ -99,7 +109,7 @@ public class SleepChartFragment extends AbstractChartFragment {
Bundle savedInstanceState) { Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_sleepchart, container, false); View rootView = inflater.inflate(R.layout.fragment_sleepchart, container, false);
mActivityChart = (BarLineChartBase) rootView.findViewById(R.id.sleepchart); mActivityChart = (CombinedChart) rootView.findViewById(R.id.sleepchart);
mSleepAmountChart = (PieChart) rootView.findViewById(R.id.sleepchart_pie_light_deep); mSleepAmountChart = (PieChart) rootView.findViewById(R.id.sleepchart_pie_light_deep);
setupActivityChart(); setupActivityChart();
@ -132,6 +142,7 @@ public class SleepChartFragment extends AbstractChartFragment {
mSleepAmountChart.setDescription(""); mSleepAmountChart.setDescription("");
mSleepAmountChart.setNoDataTextDescription(""); mSleepAmountChart.setNoDataTextDescription("");
mSleepAmountChart.setNoDataText(""); mSleepAmountChart.setNoDataText("");
mSleepAmountChart.getLegend().setEnabled(false);
} }
private void setupActivityChart() { private void setupActivityChart() {
@ -151,6 +162,7 @@ public class SleepChartFragment extends AbstractChartFragment {
// y.setDrawLabels(false); // y.setDrawLabels(false);
// TODO: make fixed max value optional // TODO: make fixed max value optional
y.setAxisMaxValue(1f); y.setAxisMaxValue(1f);
y.setAxisMinValue(0);
y.setDrawTopYLabelEntry(false); y.setDrawTopYLabelEntry(false);
y.setTextColor(CHART_TEXT_COLOR); y.setTextColor(CHART_TEXT_COLOR);
@ -159,10 +171,12 @@ public class SleepChartFragment extends AbstractChartFragment {
YAxis yAxisRight = mActivityChart.getAxisRight(); YAxis yAxisRight = mActivityChart.getAxisRight();
yAxisRight.setDrawGridLines(false); yAxisRight.setDrawGridLines(false);
yAxisRight.setEnabled(false); yAxisRight.setEnabled(supportsHeartrate());
yAxisRight.setDrawLabels(false); yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(false); yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR); yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE);
} }
protected void setupLegend(Chart chart) { protected void setupLegend(Chart chart) {
@ -172,6 +186,10 @@ public class SleepChartFragment extends AbstractChartFragment {
legendLabels.add(akLightSleep.label); legendLabels.add(akLightSleep.label);
legendColors.add(akDeepSleep.color); legendColors.add(akDeepSleep.color);
legendLabels.add(akDeepSleep.label); legendLabels.add(akDeepSleep.label);
if (supportsHeartrate()) {
legendColors.add(HEARTRATE_COLOR);
legendLabels.add(HEARTRATE_LABEL);
}
chart.getLegend().setCustom(legendColors, legendLabels); chart.getLegend().setCustom(legendColors, legendLabels);
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR); chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
} }
@ -187,4 +205,40 @@ public class SleepChartFragment extends AbstractChartFragment {
mActivityChart.animateX(ANIM_TIME, Easing.EasingOption.EaseInOutQuart); mActivityChart.animateX(ANIM_TIME, Easing.EasingOption.EaseInOutQuart);
mSleepAmountChart.invalidate(); mSleepAmountChart.invalidate();
} }
private static class MySleepChartsData extends ChartsData {
private String totalSleep;
private final PieData pieData;
public MySleepChartsData(String totalSleep, PieData pieData) {
this.totalSleep = totalSleep;
this.pieData = pieData;
}
public PieData getPieData() {
return pieData;
}
public CharSequence getTotalSleep() {
return totalSleep;
}
}
private static class MyChartsData extends ChartsData {
private final DefaultChartsData chartsData;
private final MySleepChartsData pieData;
public MyChartsData(MySleepChartsData pieData, DefaultChartsData chartsData) {
this.pieData = pieData;
this.chartsData = chartsData;
}
public MySleepChartsData getPieData() {
return pieData;
}
public DefaultChartsData getChartsData() {
return chartsData;
}
}
} }

View File

@ -6,8 +6,8 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.Chart; import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.CombinedChart;
import com.github.mikephil.charting.charts.PieChart; import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.components.LimitLine; import com.github.mikephil.charting.components.LimitLine;
import com.github.mikephil.charting.components.XAxis; import com.github.mikephil.charting.components.XAxis;
@ -15,6 +15,7 @@ import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData; import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet; import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry; import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.CombinedData;
import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.PieData; import com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.data.PieDataSet; import com.github.mikephil.charting.data.PieDataSet;
@ -41,19 +42,30 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
private Locale mLocale; private Locale mLocale;
private int mTargetSteps = 10000; private int mTargetSteps = 10000;
private BarLineChartBase mWeekStepsChart;
private PieChart mTodayStepsChart; private PieChart mTodayStepsChart;
private CombinedChart mWeekStepsChart;
@Override @Override
protected void refreshInBackground(DBHandler db, GBDevice device) { protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
ChartsHost chartsHost = getChartsHost(); Calendar day = Calendar.getInstance();
if (chartsHost != null) { day.setTime(chartsHost.getEndDate());
Calendar day = Calendar.getInstance(); //NB: we could have omitted the day, but this way we can move things to the past easily
day.setTime(chartsHost.getEndDate()); DaySteps daySteps = refreshDaySteps(db, day, device);
//NB: we could have omitted the day, but this way we can move things to the past easily DefaultChartsData weekBeforeStepsData = refreshWeekBeforeSteps(db, mWeekStepsChart, day, device);
refreshDaySteps(db, mTodayStepsChart, day, device);
refreshWeekBeforeSteps(db, mWeekStepsChart, day, device); return new MyChartsData(daySteps, weekBeforeStepsData);
} }
@Override
protected void updateChartsnUIThread(ChartsData chartsData) {
MyChartsData mcd = (MyChartsData) chartsData;
// setupLegend(mWeekStepsChart);
mTodayStepsChart.setCenterText(NumberFormat.getNumberInstance(mLocale).format(mcd.getDaySteps().totalSteps));
mTodayStepsChart.setData(mcd.getDaySteps().data);
mWeekStepsChart.setData(mcd.getWeekBeforeStepsData().getCombinedData());
mWeekStepsChart.getLegend().setEnabled(false);
} }
@Override @Override
@ -62,7 +74,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
mTodayStepsChart.invalidate(); mTodayStepsChart.invalidate();
} }
private void refreshWeekBeforeSteps(DBHandler db, BarLineChartBase barChart, Calendar day, GBDevice device) { private DefaultChartsData refreshWeekBeforeSteps(DBHandler db, CombinedChart combinedChart, Calendar day, GBDevice device) {
ActivityAnalysis analysis = new ActivityAnalysis(); ActivityAnalysis analysis = new ActivityAnalysis();
@ -80,24 +92,25 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
BarDataSet set = new BarDataSet(entries, ""); BarDataSet set = new BarDataSet(entries, "");
set.setColor(akActivity.color); set.setColor(akActivity.color);
BarData data = new BarData(labels, set); BarData barData = new BarData(labels, set);
data.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false); barData.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false);
LimitLine target = new LimitLine(mTargetSteps); LimitLine target = new LimitLine(mTargetSteps);
barChart.getAxisLeft().removeAllLimitLines(); combinedChart.getAxisLeft().removeAllLimitLines();
barChart.getAxisLeft().addLimitLine(target); combinedChart.getAxisLeft().addLimitLine(target);
setupLegend(barChart); CombinedData combinedData = new CombinedData(labels);
barChart.setData(data); combinedData.setData(barData);
barChart.getLegend().setEnabled(false); return new DefaultChartsData(combinedData);
} }
private void refreshDaySteps(DBHandler db, PieChart pieChart, Calendar day, GBDevice device) {
private DaySteps refreshDaySteps(DBHandler db, Calendar day, GBDevice device) {
ActivityAnalysis analysis = new ActivityAnalysis(); ActivityAnalysis analysis = new ActivityAnalysis();
int totalSteps = analysis.calculateTotalSteps(getSamplesOfDay(db, day, device)); int totalSteps = analysis.calculateTotalSteps(getSamplesOfDay(db, day, device));
pieChart.setCenterText(NumberFormat.getNumberInstance(mLocale).format(totalSteps));
PieData data = new PieData(); PieData data = new PieData();
List<Entry> entries = new ArrayList<>(); List<Entry> entries = new ArrayList<>();
List<Integer> colors = new ArrayList<>(); List<Integer> colors = new ArrayList<>();
@ -119,9 +132,8 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
data.setDataSet(set); data.setDataSet(set);
//this hides the values (numeric) added to the set. These would be shown aside the strings set with addXValue above //this hides the values (numeric) added to the set. These would be shown aside the strings set with addXValue above
data.setDrawValues(false); data.setDrawValues(false);
pieChart.setData(data);
pieChart.getLegend().setEnabled(false); return new DaySteps(data, totalSteps);
} }
@Override @Override
@ -137,7 +149,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
mTargetSteps = MiBandCoordinator.getFitnessGoal(device.getAddress()); mTargetSteps = MiBandCoordinator.getFitnessGoal(device.getAddress());
} }
mWeekStepsChart = (BarLineChartBase) rootView.findViewById(R.id.sleepchart); mWeekStepsChart = (CombinedChart) rootView.findViewById(R.id.sleepchart);
mTodayStepsChart = (PieChart) rootView.findViewById(R.id.sleepchart_pie_light_deep); mTodayStepsChart = (PieChart) rootView.findViewById(R.id.sleepchart_pie_light_deep);
setupWeekStepsChart(); setupWeekStepsChart();
@ -160,6 +172,8 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
mTodayStepsChart.setDescription(getContext().getString(R.string.weeksteps_today_steps_description, mTargetSteps)); mTodayStepsChart.setDescription(getContext().getString(R.string.weeksteps_today_steps_description, mTargetSteps));
mTodayStepsChart.setNoDataTextDescription(""); mTodayStepsChart.setNoDataTextDescription("");
mTodayStepsChart.setNoDataText(""); mTodayStepsChart.setNoDataText("");
mTodayStepsChart.getLegend().setEnabled(false);
// setupLegend(mTodayStepsChart);
} }
private void setupWeekStepsChart() { private void setupWeekStepsChart() {
@ -192,12 +206,12 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
} }
protected void setupLegend(Chart chart) { protected void setupLegend(Chart chart) {
List<Integer> legendColors = new ArrayList<>(1); // List<Integer> legendColors = new ArrayList<>(1);
List<String> legendLabels = new ArrayList<>(1); // List<String> legendLabels = new ArrayList<>(1);
legendColors.add(akActivity.color); // legendColors.add(akActivity.color);
legendLabels.add(getContext().getString(R.string.chart_steps)); // legendLabels.add(getContext().getString(R.string.chart_steps));
chart.getLegend().setCustom(legendColors, legendLabels); // chart.getLegend().setCustom(legendColors, legendLabels);
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR); // chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
} }
private List<ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, GBDevice device) { private List<ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, GBDevice device) {
@ -222,4 +236,32 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
return super.getAllSamples(db, device, tsFrom, tsTo); return super.getAllSamples(db, device, tsFrom, tsTo);
} }
private static class DaySteps {
private final PieData data;
private final int totalSteps;
public DaySteps(PieData data, int totalSteps) {
this.data = data;
this.totalSteps = totalSteps;
}
}
private static class MyChartsData extends ChartsData {
private final DefaultChartsData weekBeforeStepsData;
private final DaySteps daySteps;
public MyChartsData(DaySteps daySteps, DefaultChartsData weekBeforeStepsData) {
this.daySteps = daySteps;
this.weekBeforeStepsData = weekBeforeStepsData;
}
public DaySteps getDaySteps() {
return daySteps;
}
public DefaultChartsData getWeekBeforeStepsData() {
return weekBeforeStepsData;
}
}
} }

View File

@ -15,6 +15,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms; import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm; import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
@ -152,7 +153,7 @@ public class GBAlarmListAdapter extends ArrayAdapter<GBAlarm> {
if (isOn) { if (isOn) {
view.setTextColor(Color.BLUE); view.setTextColor(Color.BLUE);
} else { } else {
view.setTextColor(Color.BLACK); view.setTextColor(GBApplication.getTextColor(mContext));
} }
} }
} }

View File

@ -8,14 +8,17 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import java.util.Collections;
import java.util.List; import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
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.ItemWithDetails;
/** /**
* Adapter for displaying GBDevice instances. * Adapter for displaying GBDevice instances.
@ -32,7 +35,7 @@ public class GBDeviceAdapter extends ArrayAdapter<GBDevice> {
@Override @Override
public View getView(int position, View view, ViewGroup parent) { public View getView(int position, View view, ViewGroup parent) {
GBDevice device = getItem(position); final GBDevice device = getItem(position);
if (view == null) { if (view == null) {
LayoutInflater inflater = (LayoutInflater) context LayoutInflater inflater = (LayoutInflater) context
@ -42,33 +45,60 @@ public class GBDeviceAdapter extends ArrayAdapter<GBDevice> {
} }
TextView deviceStatusLabel = (TextView) view.findViewById(R.id.device_status); TextView deviceStatusLabel = (TextView) view.findViewById(R.id.device_status);
TextView deviceNameLabel = (TextView) view.findViewById(R.id.device_name); TextView deviceNameLabel = (TextView) view.findViewById(R.id.device_name);
TextView deviceInfoLabel = (TextView) view.findViewById(R.id.device_info); final ListView deviceInfoList = (ListView) view.findViewById(R.id.device_item_infos);
ItemWithDetailsAdapter infoAdapter = new ItemWithDetailsAdapter(context, device.getDeviceInfos());
infoAdapter.setHorizontalAlignment(true);
deviceInfoList.setAdapter(infoAdapter);
TextView batteryLabel = (TextView) view.findViewById(R.id.battery_label);
TextView batteryStatusLabel = (TextView) view.findViewById(R.id.battery_status); TextView batteryStatusLabel = (TextView) view.findViewById(R.id.battery_status);
ImageView deviceImageView = (ImageView) view.findViewById(R.id.device_image); final ImageView deviceImageView = (ImageView) view.findViewById(R.id.device_image);
ImageView deviceInfoView = (ImageView) view.findViewById(R.id.device_info_image);
ProgressBar busyIndicator = (ProgressBar) view.findViewById(R.id.device_busy_indicator); ProgressBar busyIndicator = (ProgressBar) view.findViewById(R.id.device_busy_indicator);
deviceNameLabel.setText(getUniqueDeviceName(device)); deviceNameLabel.setText(getUniqueDeviceName(device));
deviceInfoLabel.setText(device.getInfoString());
if (device.isBusy()) { if (device.isBusy()) {
deviceStatusLabel.setText(device.getBusyTask()); deviceStatusLabel.setText(device.getBusyTask());
busyIndicator.setVisibility(View.VISIBLE); busyIndicator.setVisibility(View.VISIBLE);
batteryStatusLabel.setVisibility(View.GONE); batteryLabel.setVisibility(View.INVISIBLE);
deviceInfoLabel.setVisibility(View.GONE); batteryStatusLabel.setVisibility(View.INVISIBLE);
} else { } else {
deviceStatusLabel.setText(device.getStateString()); deviceStatusLabel.setText(device.getStateString());
busyIndicator.setVisibility(View.GONE); busyIndicator.setVisibility(View.INVISIBLE);
batteryLabel.setVisibility(View.VISIBLE);
batteryStatusLabel.setVisibility(View.VISIBLE); batteryStatusLabel.setVisibility(View.VISIBLE);
deviceInfoLabel.setVisibility(View.VISIBLE);
} }
boolean showInfoIcon = device.hasDeviceInfos() && !device.isBusy();
deviceInfoView.setVisibility(showInfoIcon ? View.VISIBLE : View.GONE);
deviceInfoView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (deviceInfoList.getVisibility() == View.VISIBLE) {
deviceInfoList.setVisibility(View.GONE);
} else {
ArrayAdapter adapter = (ArrayAdapter) deviceInfoList.getAdapter();
adapter.clear();
List<ItemWithDetails> infos = device.getDeviceInfos();
Collections.sort(infos);
adapter.addAll(infos);
justifyListViewHeightBasedOnChildren(deviceInfoList);
deviceInfoList.setVisibility(View.VISIBLE);
deviceInfoList.setFocusable(false);
}
}
});
short batteryLevel = device.getBatteryLevel(); short batteryLevel = device.getBatteryLevel();
if (batteryLevel != GBDevice.BATTERY_UNKNOWN) { if (batteryLevel != GBDevice.BATTERY_UNKNOWN) {
batteryStatusLabel.setText("BAT: " + device.getBatteryLevel() + "%"); batteryLabel.setText("BAT:");
batteryStatusLabel.setText(device.getBatteryLevel() + "%");
BatteryState batteryState = device.getBatteryState(); BatteryState batteryState = device.getBatteryState();
if (BatteryState.BATTERY_LOW.equals(batteryState)) { if (BatteryState.BATTERY_LOW.equals(batteryState)) {
batteryLabel.setTextColor(Color.RED);
batteryStatusLabel.setTextColor(Color.RED); batteryStatusLabel.setTextColor(Color.RED);
} else { } else {
batteryLabel.setTextColor(ContextCompat.getColor(getContext(), R.color.secondarytext));
batteryStatusLabel.setTextColor(ContextCompat.getColor(getContext(), R.color.secondarytext)); batteryStatusLabel.setTextColor(ContextCompat.getColor(getContext(), R.color.secondarytext));
if (BatteryState.BATTERY_CHARGING.equals(batteryState) || if (BatteryState.BATTERY_CHARGING.equals(batteryState) ||
@ -77,27 +107,67 @@ public class GBDeviceAdapter extends ArrayAdapter<GBDevice> {
} }
} }
} else { } else {
batteryLabel.setText("");
batteryStatusLabel.setText(""); batteryStatusLabel.setText("");
} }
switch (device.getType()) { switch (device.getType()) {
case PEBBLE: case PEBBLE:
deviceImageView.setImageResource(R.drawable.ic_device_pebble); if (device.isConnected()) {
deviceImageView.setImageResource(R.drawable.ic_device_pebble);
} else {
deviceImageView.setImageResource(R.drawable.ic_device_pebble_disabled);
}
break; break;
case MIBAND: case MIBAND:
deviceImageView.setImageResource(R.drawable.ic_device_miband); if (device.isConnected()) {
deviceImageView.setImageResource(R.drawable.ic_device_miband);
} else {
deviceImageView.setImageResource(R.drawable.ic_device_miband_disabled);
}
break; break;
default: default:
deviceImageView.setImageResource(R.drawable.ic_launcher); if (device.isConnected()) {
deviceImageView.setImageResource(R.drawable.ic_launcher);
} else {
deviceImageView.setImageResource(R.drawable.ic_device_default_disabled);
}
} }
return view; return view;
} }
public void justifyListViewHeightBasedOnChildren(ListView listView) {
ArrayAdapter adapter = (ArrayAdapter) listView.getAdapter();
if (adapter == null) {
return;
}
ViewGroup vg = listView;
int totalHeight = 0;
for (int i = 0; i < adapter.getCount(); i++) {
View listItem = adapter.getView(i, null, vg);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams par = listView.getLayoutParams();
par.height = totalHeight + (listView.getDividerHeight() * (adapter.getCount() - 1));
listView.setLayoutParams(par);
listView.requestLayout();
}
private String getUniqueDeviceName(GBDevice device) { private String getUniqueDeviceName(GBDevice device) {
String deviceName = device.getName(); String deviceName = device.getName();
if (!isUniqueDeviceName(device, deviceName)) { if (!isUniqueDeviceName(device, deviceName)) {
deviceName = deviceName + " " + device.getShortAddress(); if (device.getHardwareVersion() != null) {
deviceName = deviceName + " " + device.getHardwareVersion();
if (!isUniqueDeviceName(device, deviceName)) {
deviceName = deviceName + " " + device.getShortAddress();
}
} else {
deviceName = deviceName + " " + device.getShortAddress();
}
} }
return deviceName; return deviceName;
} }

View File

@ -18,7 +18,12 @@ import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
*/ */
public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> { public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
public static final int SIZE_SMALL = 1;
public static final int SIZE_MEDIUM = 2;
public static final int SIZE_LARGE = 3;
private final Context context; private 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, 0, items);
@ -26,6 +31,10 @@ public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
this.context = context; this.context = context;
} }
public void setHorizontalAlignment(boolean horizontalAlignment) {
this.horizontalAlignment = horizontalAlignment;
}
@Override @Override
public View getView(int position, View view, ViewGroup parent) { public View getView(int position, View view, ViewGroup parent) {
ItemWithDetails item = getItem(position); ItemWithDetails item = getItem(position);
@ -34,7 +43,18 @@ public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
LayoutInflater inflater = (LayoutInflater) context LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.item_with_details, parent, false); if (horizontalAlignment) {
view = inflater.inflate(R.layout.item_with_details_horizontal, parent, false);
} else {
switch (size) {
case SIZE_SMALL:
view = inflater.inflate(R.layout.item_with_details_small, parent, false);
break;
default:
view = inflater.inflate(R.layout.item_with_details, parent, false);
break;
}
}
} }
ImageView iconView = (ImageView) view.findViewById(R.id.item_image); ImageView iconView = (ImageView) view.findViewById(R.id.item_image);
TextView nameView = (TextView) view.findViewById(R.id.item_name); TextView nameView = (TextView) view.findViewById(R.id.item_name);
@ -46,4 +66,12 @@ public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
return view; return view;
} }
public void setSize(int size) {
this.size = size;
}
public int getSize() {
return size;
}
} }

View File

@ -6,16 +6,16 @@ import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor; import android.database.Cursor;
import android.database.MatrixCursor; import android.database.MatrixCursor;
import android.net.Uri; import android.net.Uri;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class PebbleContentProvider extends ContentProvider { public class PebbleContentProvider extends ContentProvider {
@ -59,8 +59,8 @@ public class PebbleContentProvider extends ContentProvider {
MatrixCursor mc = new MatrixCursor(columnNames); MatrixCursor mc = new MatrixCursor(columnNames);
int connected = 0; int connected = 0;
int appMessage = 0; int appMessage = 0;
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this.getContext()); Prefs prefs = GBApplication.getPrefs();
if (sharedPrefs.getBoolean("pebble_enable_pebblekit", false)) { if (prefs.getBoolean("pebble_enable_pebblekit", false)) {
appMessage = 1; appMessage = 1;
} }
if (mGBDevice != null && mGBDevice.getType() == DeviceType.PEBBLE && mGBDevice.isInitialized()) { if (mGBDevice != null && mGBDevice.getType() == DeviceType.PEBBLE && mGBDevice.isInitialized()) {

View File

@ -22,6 +22,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.DATABASE_NAME; import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.DATABASE_NAME;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_CUSTOM_SHORT;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_INTENSITY; import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_INTENSITY;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_PROVIDER; import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_PROVIDER;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS; import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS;
@ -33,7 +34,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
private static final Logger LOG = LoggerFactory.getLogger(ActivityDatabaseHandler.class); private static final Logger LOG = LoggerFactory.getLogger(ActivityDatabaseHandler.class);
private static final int DATABASE_VERSION = 5; private static final int DATABASE_VERSION = 7;
public ActivityDatabaseHandler(Context context) { public ActivityDatabaseHandler(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); super(context, DATABASE_NAME, null, DATABASE_VERSION);
@ -51,6 +52,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
@Override @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
LOG.info("ActivityDatabase: schema upgrade requested from " + oldVersion + " to " + newVersion);
try { try {
for (int i = oldVersion + 1; i <= newVersion; i++) { for (int i = oldVersion + 1; i <= newVersion; i++) {
DBUpdateScript updater = getUpdateScript(db, i); DBUpdateScript updater = getUpdateScript(db, i);
@ -68,6 +70,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
@Override @Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
LOG.info("ActivityDatabase: schema downgrade requested from " + oldVersion + " to " + newVersion);
try { try {
for (int i = oldVersion; i >= newVersion; i--) { for (int i = oldVersion; i >= newVersion; i--) {
DBUpdateScript updater = getUpdateScript(db, i); DBUpdateScript updater = getUpdateScript(db, i);
@ -101,6 +104,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
values.put(KEY_PROVIDER, sample.getProvider().getID()); values.put(KEY_PROVIDER, sample.getProvider().getID());
values.put(KEY_INTENSITY, sample.getRawIntensity()); values.put(KEY_INTENSITY, sample.getRawIntensity());
values.put(KEY_STEPS, sample.getSteps()); values.put(KEY_STEPS, sample.getSteps());
values.put(KEY_CUSTOM_SHORT, sample.getCustomValue());
values.put(KEY_TYPE, sample.getRawKind()); values.put(KEY_TYPE, sample.getRawKind());
db.insert(TABLE_GBACTIVITYSAMPLES, null, values); db.insert(TABLE_GBACTIVITYSAMPLES, null, values);
@ -110,14 +114,15 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
/** /**
* Adds the a new sample to the database * Adds the a new sample to the database
* *
* @param timestamp the timestamp of the same, second-based! * @param timestamp the timestamp of the same, second-based!
* @param provider the SampleProvider ID * @param provider the SampleProvider ID
* @param intensity the sample's raw intensity value * @param intensity the sample's raw intensity value
* @param steps the sample's steps value * @param steps the sample's steps value
* @param kind the raw activity kind of the sample * @param kind the raw activity kind of the sample
* @param customShortValue
*/ */
@Override @Override
public void addGBActivitySample(int timestamp, byte provider, short intensity, short steps, byte kind) { public void addGBActivitySample(int timestamp, int provider, int intensity, int steps, int kind, int customShortValue) {
if (intensity < 0) { if (intensity < 0) {
LOG.error("negative intensity received, ignoring"); LOG.error("negative intensity received, ignoring");
intensity = 0; intensity = 0;
@ -127,6 +132,11 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
steps = 0; steps = 0;
} }
if (customShortValue < 0) {
LOG.error("negative short value received, ignoring");
customShortValue = 0;
}
try (SQLiteDatabase db = this.getWritableDatabase()) { try (SQLiteDatabase db = this.getWritableDatabase()) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(KEY_TIMESTAMP, timestamp); values.put(KEY_TIMESTAMP, timestamp);
@ -134,6 +144,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
values.put(KEY_INTENSITY, intensity); values.put(KEY_INTENSITY, intensity);
values.put(KEY_STEPS, steps); values.put(KEY_STEPS, steps);
values.put(KEY_TYPE, kind); values.put(KEY_TYPE, kind);
values.put(KEY_CUSTOM_SHORT, customShortValue);
db.insert(TABLE_GBACTIVITYSAMPLES, null, values); db.insert(TABLE_GBACTIVITYSAMPLES, null, values);
} }
@ -144,8 +155,8 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
try (SQLiteDatabase db = this.getWritableDatabase()) { try (SQLiteDatabase db = this.getWritableDatabase()) {
String sql = "INSERT INTO " + TABLE_GBACTIVITYSAMPLES + " (" + KEY_TIMESTAMP + "," + String sql = "INSERT INTO " + TABLE_GBACTIVITYSAMPLES + " (" + KEY_TIMESTAMP + "," +
KEY_PROVIDER + "," + KEY_INTENSITY + "," + KEY_STEPS + "," + KEY_TYPE + ")" + KEY_PROVIDER + "," + KEY_INTENSITY + "," + KEY_STEPS + "," + KEY_TYPE + "," + KEY_CUSTOM_SHORT + ")" +
" VALUES (?,?,?,?,?);"; " VALUES (?,?,?,?,?,?);";
SQLiteStatement statement = db.compileStatement(sql); SQLiteStatement statement = db.compileStatement(sql);
db.beginTransaction(); db.beginTransaction();
@ -156,6 +167,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
statement.bindLong(3, activitySample.getRawIntensity()); statement.bindLong(3, activitySample.getRawIntensity());
statement.bindLong(4, activitySample.getSteps()); statement.bindLong(4, activitySample.getSteps());
statement.bindLong(5, activitySample.getRawKind()); statement.bindLong(5, activitySample.getRawKind());
statement.bindLong(6, activitySample.getCustomValue());
statement.execute(); statement.execute();
} }
db.setTransactionSuccessful(); db.setTransactionSuccessful();
@ -209,16 +221,20 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
try (SQLiteDatabase db = this.getReadableDatabase()) { try (SQLiteDatabase db = this.getReadableDatabase()) {
try (Cursor cursor = db.query(TABLE_GBACTIVITYSAMPLES, null, where, null, null, null, order)) { try (Cursor cursor = db.query(TABLE_GBACTIVITYSAMPLES, null, where, null, null, null, order)) {
LOG.info("Activity query result: " + cursor.getCount() + " samples"); LOG.info("Activity query result: " + cursor.getCount() + " samples");
if (cursor.moveToFirst()) { int colTimeStamp = cursor.getColumnIndex(KEY_TIMESTAMP);
do { int colIntensity = cursor.getColumnIndex(KEY_INTENSITY);
GBActivitySample sample = new GBActivitySample( int colSteps = cursor.getColumnIndex(KEY_STEPS);
provider, int colType = cursor.getColumnIndex(KEY_TYPE);
cursor.getInt(cursor.getColumnIndex(KEY_TIMESTAMP)), int colCustomShort = cursor.getColumnIndex(KEY_CUSTOM_SHORT);
cursor.getShort(cursor.getColumnIndex(KEY_INTENSITY)), while (cursor.moveToNext()) {
cursor.getShort(cursor.getColumnIndex(KEY_STEPS)), GBActivitySample sample = new GBActivitySample(
(byte) cursor.getShort(cursor.getColumnIndex(KEY_TYPE))); provider,
samples.add(sample); cursor.getInt(colTimeStamp),
} while (cursor.moveToNext()); cursor.getInt(colIntensity),
cursor.getInt(colSteps),
cursor.getInt(colType),
cursor.getInt(colCustomShort));
samples.add(sample);
} }
} }
} }
@ -232,7 +248,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
} }
StringBuilder builder = new StringBuilder(" and ("); StringBuilder builder = new StringBuilder(" and (");
byte[] dbActivityTypes = ActivityKind.mapToDBActivityTypes(activityTypes, provider); int[] dbActivityTypes = ActivityKind.mapToDBActivityTypes(activityTypes, provider);
for (int i = 0; i < dbActivityTypes.length; i++) { for (int i = 0; i < dbActivityTypes.length; i++) {
builder.append(" type=").append(dbActivityTypes[i]); builder.append(" type=").append(dbActivityTypes[i]);
if (i + 1 < dbActivityTypes.length) { if (i + 1 < dbActivityTypes.length) {
@ -242,4 +258,50 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
builder.append(')'); builder.append(')');
return builder.toString(); return builder.toString();
} }
@Override
public void changeStoredSamplesType(int timestampFrom, int timestampTo, int kind, SampleProvider provider) {
try (SQLiteDatabase db = this.getReadableDatabase()) {
String sql = "UPDATE " + TABLE_GBACTIVITYSAMPLES + " SET " + KEY_TYPE + "= ? WHERE "
+ KEY_PROVIDER + " = ? AND "
+ KEY_TIMESTAMP + " >= ? AND " + KEY_TIMESTAMP + " < ? ;"; //do not use BETWEEN because the range is inclusive in that case!
SQLiteStatement statement = db.compileStatement(sql);
statement.bindLong(1, kind);
statement.bindLong(2, provider.getID());
statement.bindLong(3, timestampFrom);
statement.bindLong(4, timestampTo);
statement.execute();
}
}
@Override
public void changeStoredSamplesType(int timestampFrom, int timestampTo, int fromKind, int toKind, SampleProvider provider) {
try (SQLiteDatabase db = this.getReadableDatabase()) {
String sql = "UPDATE " + TABLE_GBACTIVITYSAMPLES + " SET " + KEY_TYPE + "= ? WHERE "
+ KEY_TYPE + " = ? AND "
+ KEY_PROVIDER + " = ? AND "
+ KEY_TIMESTAMP + " >= ? AND " + KEY_TIMESTAMP + " < ? ;"; //do not use BETWEEN because the range is inclusive in that case!
SQLiteStatement statement = db.compileStatement(sql);
statement.bindLong(1, toKind);
statement.bindLong(2, fromKind);
statement.bindLong(3, provider.getID());
statement.bindLong(4, timestampFrom);
statement.bindLong(5, timestampTo);
statement.execute();
}
}
@Override
public int fetchLatestTimestamp(SampleProvider provider) {
try (SQLiteDatabase db = this.getReadableDatabase()) {
try (Cursor cursor = db.query(TABLE_GBACTIVITYSAMPLES, new String[]{KEY_TIMESTAMP}, KEY_PROVIDER + "=" + String.valueOf(provider.getID()), null, null, null, KEY_TIMESTAMP + " DESC", "1")) {
if (cursor.moveToFirst()) {
return cursor.getInt(0);
}
}
}
return -1;
}
} }

View File

@ -10,5 +10,6 @@ public class DBConstants {
public static final String KEY_PROVIDER = "provider"; public static final String KEY_PROVIDER = "provider";
public static final String KEY_INTENSITY = "intensity"; public static final String KEY_INTENSITY = "intensity";
public static final String KEY_STEPS = "steps"; public static final String KEY_STEPS = "steps";
public static final String KEY_CUSTOM_SHORT = "customShort";
public static final String KEY_TYPE = "type"; public static final String KEY_TYPE = "type";
} }

View File

@ -24,9 +24,16 @@ public interface DBHandler {
List<ActivitySample> getSleepSamples(int tsFrom, int tsTo, SampleProvider provider); List<ActivitySample> getSleepSamples(int tsFrom, int tsTo, SampleProvider provider);
void addGBActivitySample(int timestamp, byte provider, short intensity, short steps, byte kind); void addGBActivitySample(int timestamp, int provider, int intensity, int steps, int kind, int heartrate);
void addGBActivitySamples(ActivitySample[] activitySamples); void addGBActivitySamples(ActivitySample[] activitySamples);
SQLiteDatabase getWritableDatabase(); SQLiteDatabase getWritableDatabase();
void changeStoredSamplesType(int timestampFrom, int timestampTo, int kind, SampleProvider provider);
void changeStoredSamplesType(int timestampFrom, int timestampTo, int fromKind, int toKind, SampleProvider provider);
int fetchLatestTimestamp(SampleProvider provider);
} }

View File

@ -1,6 +1,7 @@
package nodomain.freeyourgadget.gadgetbridge.database; package nodomain.freeyourgadget.gadgetbridge.database;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
@ -70,6 +71,22 @@ public class DBHelper {
db.execSQL(statement); db.execSQL(statement);
} }
public static boolean existsColumn(String tableName, String columnName, SQLiteDatabase db) {
try (Cursor res = db.rawQuery("PRAGMA table_info('" + tableName + "')", null)) {
int index = res.getColumnIndex("name");
if (index < 1) {
return false; // something's really wrong
}
while (res.moveToNext()) {
String cn = res.getString(index);
if (columnName.equals(cn)) {
return true;
}
}
}
return false;
}
/** /**
* WITHOUT ROWID is only available with sqlite 3.8.2, which is available * WITHOUT ROWID is only available with sqlite 3.8.2, which is available
* with Lollipop and later. * with Lollipop and later.

View File

@ -4,6 +4,7 @@ import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_CUSTOM_SHORT;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_INTENSITY; import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_INTENSITY;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_PROVIDER; import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_PROVIDER;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS; import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS;
@ -19,6 +20,7 @@ public class ActivityDBCreationScript {
+ KEY_INTENSITY + " SMALLINT," + KEY_INTENSITY + " SMALLINT,"
+ KEY_STEPS + " TINYINT," + KEY_STEPS + " TINYINT,"
+ KEY_TYPE + " TINYINT," + KEY_TYPE + " TINYINT,"
+ KEY_CUSTOM_SHORT + " INT,"
+ " PRIMARY KEY (" + KEY_TIMESTAMP + "," + KEY_PROVIDER + ") ON CONFLICT REPLACE)" + DBHelper.getWithoutRowId(); + " PRIMARY KEY (" + KEY_TIMESTAMP + "," + KEY_PROVIDER + ") ON CONFLICT REPLACE)" + DBHelper.getWithoutRowId();
db.execSQL(CREATE_GBACTIVITYSAMPLES_TABLE); db.execSQL(CREATE_GBACTIVITYSAMPLES_TABLE);
} }

View File

@ -5,27 +5,23 @@ import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript; import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_PROVIDER; import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_CUSTOM_SHORT;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS; import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TIMESTAMP;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_STEPS_PER_DAY;
/** /**
* Adds a table "STEPS_PER_DAY". * Adds a column "customShort" to the table "GBActivitySamples"
*/ */
public class ActivityDBUpdate_6 implements DBUpdateScript { public class ActivityDBUpdate_6 implements DBUpdateScript {
@Override @Override
public void upgradeSchema(SQLiteDatabase db) { public void upgradeSchema(SQLiteDatabase db) {
String CREATE_STEPS_PER_DAY_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_STEPS_PER_DAY + " (" if (!DBHelper.existsColumn(TABLE_GBACTIVITYSAMPLES, KEY_CUSTOM_SHORT, db)) {
+ KEY_TIMESTAMP + " INT," String ADD_COLUMN_CUSTOM_SHORT = "ALTER TABLE " + TABLE_GBACTIVITYSAMPLES + " ADD COLUMN "
+ KEY_PROVIDER + " TINYINT," + KEY_CUSTOM_SHORT + " INT;";
+ KEY_STEPS + " MEDIUMINT," db.execSQL(ADD_COLUMN_CUSTOM_SHORT);
+ " PRIMARY KEY (" + KEY_TIMESTAMP + "," + KEY_PROVIDER + ") ON CONFLICT REPLACE)" + DBHelper.getWithoutRowId(); }
db.execSQL(CREATE_STEPS_PER_DAY_TABLE);
} }
@Override @Override
public void downgradeSchema(SQLiteDatabase db) { public void downgradeSchema(SQLiteDatabase db) {
DBHelper.dropTable(TABLE_STEPS_PER_DAY, db);
} }
} }

View File

@ -0,0 +1,8 @@
package nodomain.freeyourgadget.gadgetbridge.database.schema;
/**
* Bugfix for users who installed 0.8.1 cleanly, i.e. without any previous
* database. Perform Update script 6 again.
*/
public class ActivityDBUpdate_7 extends ActivityDBUpdate_6 {
}

View File

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

View File

@ -0,0 +1,22 @@
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
public class GBDeviceEventDisplayMessage {
public String message;
public int duration;
public int severity;
/**
* An event for displaying a message to the user. How the message is displayed
* is a detail of the current activity, which needs to listen to the Intent
* GB.ACTION_DISPLAY_MESSAGE.
*
* @param message
* @param duration
* @param severity
*/
public GBDeviceEventDisplayMessage(String message, int duration, int severity) {
this.message = message;
this.duration = duration;
this.severity = severity;
}
}

View File

@ -12,7 +12,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
* This interface is implemented at least once for every supported gadget device. * This interface is implemented at least once for every supported gadget device.
* It allows Gadgetbridge to generically deal with different kinds of devices * It allows Gadgetbridge to generically deal with different kinds of devices
* without actually knowing the details of any device. * without actually knowing the details of any device.
* * <p/>
* Instances will be created as needed and asked whether they support a given * Instances will be created as needed and asked whether they support a given
* device. If a coordinator answers true, it will be used to assist in handling * device. If a coordinator answers true, it will be used to assist in handling
* the given device. * the given device.
@ -22,6 +22,7 @@ public interface DeviceCoordinator {
/** /**
* Checks whether this candidate handles the given candidate. * Checks whether this candidate handles the given candidate.
*
* @param candidate * @param candidate
* @return true if this coordinator handles the given candidate. * @return true if this coordinator handles the given candidate.
*/ */
@ -29,6 +30,7 @@ public interface DeviceCoordinator {
/** /**
* Checks whether this candidate handles the given device. * Checks whether this candidate handles the given device.
*
* @param device * @param device
* @return true if this coordinator handles the given device. * @return true if this coordinator handles the given device.
*/ */
@ -36,6 +38,7 @@ public interface DeviceCoordinator {
/** /**
* Returns the kind of device type this coordinator supports. * Returns the kind of device type this coordinator supports.
*
* @return * @return
*/ */
DeviceType getDeviceType(); DeviceType getDeviceType();
@ -43,6 +46,7 @@ public interface DeviceCoordinator {
/** /**
* Returns the Activity class to be started in order to perform a pairing of a * Returns the Activity class to be started in order to perform a pairing of a
* given device. * given device.
*
* @return * @return
*/ */
Class<? extends Activity> getPairingActivity(); Class<? extends Activity> getPairingActivity();
@ -50,6 +54,7 @@ public interface DeviceCoordinator {
/** /**
* Returns the Activity class that will be used as the primary activity * Returns the Activity class that will be used as the primary activity
* for the given device. * for the given device.
*
* @return * @return
*/ */
Class<? extends Activity> getPrimaryActivity(); Class<? extends Activity> getPrimaryActivity();
@ -57,6 +62,7 @@ public interface DeviceCoordinator {
/** /**
* Returns true if activity data fetching is supported by the device * Returns true if activity data fetching is supported by the device
* (with this coordinator). * (with this coordinator).
*
* @return * @return
*/ */
boolean supportsActivityDataFetching(); boolean supportsActivityDataFetching();
@ -65,6 +71,7 @@ public interface DeviceCoordinator {
* 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...)
* etc. * etc.
*
* @param device * @param device
* @return * @return
*/ */
@ -72,6 +79,7 @@ public interface DeviceCoordinator {
/** /**
* Returns the sample provider for the device being supported. * Returns the sample provider for the device being supported.
*
* @return * @return
*/ */
SampleProvider getSampleProvider(); SampleProvider getSampleProvider();
@ -79,6 +87,7 @@ public interface DeviceCoordinator {
/** /**
* Finds an install handler for the given uri that can install the given * Finds an install handler for the given uri that can install the given
* uri on the device being managed. * uri on the device being managed.
*
* @param uri * @param uri
* @param context * @param context
* @return the install handler or null if that uri cannot be installed on the device * @return the install handler or null if that uri cannot be installed on the device
@ -87,7 +96,17 @@ public interface DeviceCoordinator {
/** /**
* Returns true if this device/coordinator supports taking screenshots. * Returns true if this device/coordinator supports taking screenshots.
*
* @return * @return
*/ */
boolean supportsScreenshots(); boolean supportsScreenshots();
/**
* Returns true if this device/coordinator supports settig alarms.
*
* @return
*/
boolean supportsAlarmConfiguration();
int getTapString();
} }

View File

@ -1,14 +1,14 @@
package nodomain.freeyourgadget.gadgetbridge.devices; package nodomain.freeyourgadget.gadgetbridge.devices;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.UUID; import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
/** /**
* Specifies all events that GadgetBridge intends to send to the gadget device. * Specifies all events that GadgetBridge intends to send to the gadget device.
@ -22,9 +22,9 @@ public interface EventHandler {
void onSetAlarms(ArrayList<? extends Alarm> alarms); void onSetAlarms(ArrayList<? extends Alarm> alarms);
void onSetCallState(@Nullable String number, @Nullable String name, ServiceCommand command); void onSetCallState(CallSpec callSpec);
void onSetMusicInfo(String artist, String album, String track); void onSetMusicInfo(MusicSpec musicSpec);
void onEnableRealtimeSteps(boolean enable); void onEnableRealtimeSteps(boolean enable);
@ -36,11 +36,19 @@ public interface EventHandler {
void onAppDelete(UUID uuid); void onAppDelete(UUID uuid);
void onAppConfiguration(UUID appUuid, String config);
void onFetchActivityData(); void onFetchActivityData();
void onReboot(); void onReboot();
void onHeartRateTest();
void onEnableRealtimeHeartRateMeasurement(boolean enable);
void onFindDevice(boolean start); void onFindDevice(boolean start);
void onScreenshotReq(); void onScreenshotReq();
void onEnableHeartRateSleepSupport(boolean enable);
} }

View File

@ -28,7 +28,7 @@ public interface InstallHandler {
void validateInstallation(InstallActivity installActivity, GBDevice device); void validateInstallation(InstallActivity installActivity, GBDevice device);
/** /**
* Allows device specivic code to be execute just before the installation starts * Allows device specific code to be executed just before the installation starts
*/ */
void onStartInstall(GBDevice device); void onStartInstall(GBDevice device);
} }

View File

@ -1,18 +1,19 @@
package nodomain.freeyourgadget.gadgetbridge.devices; package nodomain.freeyourgadget.gadgetbridge.devices;
public interface SampleProvider { public interface SampleProvider {
byte PROVIDER_MIBAND = 0; int PROVIDER_MIBAND = 0;
byte PROVIDER_PEBBLE_MORPHEUZ = 1; int PROVIDER_PEBBLE_MORPHEUZ = 1;
byte PROVIDER_PEBBLE_GADGETBRIDGE = 2; int PROVIDER_PEBBLE_GADGETBRIDGE = 2;
byte PROVIDER_PEBBLE_MISFIT = 3; int PROVIDER_PEBBLE_MISFIT = 3;
int PROVIDER_PEBBLE_HEALTH = 4;
byte PROVIDER_UNKNOWN = 100; int PROVIDER_UNKNOWN = 100;
int normalizeType(byte rawType); int normalizeType(int rawType);
byte toRawActivityKind(int activityKind); int toRawActivityKind(int activityKind);
float normalizeIntensity(short rawIntensity); float normalizeIntensity(int rawIntensity);
byte getID(); int getID();
} }

View File

@ -15,22 +15,22 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
private static final class UnknownSampleProvider implements SampleProvider { private static final class UnknownSampleProvider implements SampleProvider {
@Override @Override
public int normalizeType(byte rawType) { public int normalizeType(int rawType) {
return ActivityKind.TYPE_UNKNOWN; return ActivityKind.TYPE_UNKNOWN;
} }
@Override @Override
public byte toRawActivityKind(int activityKind) { public int toRawActivityKind(int activityKind) {
return 0; return 0;
} }
@Override @Override
public float normalizeIntensity(short rawIntensity) { public float normalizeIntensity(int rawIntensity) {
return 0; return 0;
} }
@Override @Override
public byte getID() { public int getID() {
return PROVIDER_UNKNOWN; return PROVIDER_UNKNOWN;
} }
} }
@ -83,4 +83,14 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
public boolean supportsScreenshots() { public boolean supportsScreenshots() {
return false; return false;
} }
@Override
public boolean supportsAlarmConfiguration() {
return false;
}
@Override
public int getTapString() {
return 0;
}
} }

View File

@ -1,24 +1,21 @@
package nodomain.freeyourgadget.gadgetbridge.devices.miband; package nodomain.freeyourgadget.gadgetbridge.devices.miband;
import android.content.SharedPreferences;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public final class MiBandConst { public final class MiBandConst {
private static final Logger LOG = LoggerFactory.getLogger(MiBandConst.class); private static final Logger LOG = LoggerFactory.getLogger(MiBandConst.class);
public static final String PREF_USER_ALIAS = "mi_user_alias"; public static final String PREF_USER_ALIAS = "mi_user_alias";
public static final String PREF_USER_YEAR_OF_BIRTH = "mi_user_year_of_birth";
public static final String PREF_USER_GENDER = "mi_user_gender";
public static final String PREF_USER_HEIGHT_CM = "mi_user_height_cm";
public static final String PREF_USER_WEIGHT_KG = "mi_user_weight_kg";
public static final String PREF_MIBAND_WEARSIDE = "mi_wearside"; public static final String PREF_MIBAND_WEARSIDE = "mi_wearside";
public static final String PREF_MIBAND_ADDRESS = "development_miaddr"; // FIXME: should be prefixed mi_ public static final String PREF_MIBAND_ADDRESS = "development_miaddr"; // FIXME: should be prefixed mi_
public static final String PREF_MIBAND_ALARMS = "mi_alarms"; public static final String PREF_MIBAND_ALARMS = "mi_alarms";
public static final String PREF_MIBAND_FITNESS_GOAL = "mi_fitness_goal"; public static final String PREF_MIBAND_FITNESS_GOAL = "mi_fitness_goal";
public static final String PREF_MIBAND_DONT_ACK_TRANSFER = "mi_dont_ack_transfer"; public static final String PREF_MIBAND_DONT_ACK_TRANSFER = "mi_dont_ack_transfer";
public static final String PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR = "mi_reserve_alarm_calendar"; public static final String PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR = "mi_reserve_alarm_calendar";
public static final String PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION = "mi_hr_sleep_detection";
public static final String ORIGIN_SMS = "sms"; public static final String ORIGIN_SMS = "sms";
@ -29,20 +26,14 @@ public final class MiBandConst {
public static final String MI_1 = "1"; public static final String MI_1 = "1";
public static final String MI_1A = "1A"; public static final String MI_1A = "1A";
public static final String MI_1S = "1S"; public static final String MI_1S = "1S";
public static final String MI_AMAZFIT = "Amazfit";
public static int getNotificationPrefIntValue(String pref, String origin, SharedPreferences prefs, int defaultValue) { public static int getNotificationPrefIntValue(String pref, String origin, Prefs prefs, int defaultValue) {
String key = getNotificationPrefKey(pref, origin); String key = getNotificationPrefKey(pref, origin);
String value = null; return prefs.getInt(key, defaultValue);
try {
value = prefs.getString(key, String.valueOf(defaultValue));
return Integer.valueOf(value);
} catch (NumberFormatException ex) {
LOG.error("Error converting preference value to int: " + key + ": " + value);
return defaultValue;
}
} }
public static String getNotificationPrefStringValue(String pref, String origin, SharedPreferences prefs, String defaultValue) { public static String getNotificationPrefStringValue(String pref, String origin, Prefs prefs, String defaultValue) {
String key = getNotificationPrefKey(pref, origin); String key = getNotificationPrefKey(pref, origin);
return prefs.getString(key, defaultValue); return prefs.getString(key, defaultValue);
} }

View File

@ -2,23 +2,22 @@ package nodomain.freeyourgadget.gadgetbridge.devices.miband;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.preference.PreferenceManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Calendar;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class MiBandCoordinator extends AbstractDeviceCoordinator { public class MiBandCoordinator extends AbstractDeviceCoordinator {
private static final Logger LOG = LoggerFactory.getLogger(MiBandCoordinator.class); private static final Logger LOG = LoggerFactory.getLogger(MiBandCoordinator.class);
@ -75,6 +74,16 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
return false; return false;
} }
@Override
public boolean supportsAlarmConfiguration() {
return true;
}
@Override
public int getTapString() {
return R.string.tap_connected_device_for_activity;
}
public static boolean hasValidUserInfo() { public static boolean hasValidUserInfo() {
String dummyMacAddress = MiBandService.MAC_ADDRESS_FILTER_1_1A + ":00:00:00"; String dummyMacAddress = MiBandService.MAC_ADDRESS_FILTER_1_1A + ":00:00:00";
try { try {
@ -107,22 +116,16 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
* @throws IllegalArgumentException when the user info can not be created * @throws IllegalArgumentException when the user info can not be created
*/ */
public static UserInfo getConfiguredUserInfo(String miBandAddress) throws IllegalArgumentException { public static UserInfo getConfiguredUserInfo(String miBandAddress) throws IllegalArgumentException {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext()); ActivityUser activityUser = new ActivityUser();
int userYear = Integer.parseInt(prefs.getString(MiBandConst.PREF_USER_YEAR_OF_BIRTH, "0")); Prefs prefs = GBApplication.getPrefs();
int age = 25;
if (userYear > 1900) {
age = Calendar.getInstance().get(Calendar.YEAR) - userYear;
if (age <= 0) {
age = 25;
}
}
UserInfo info = UserInfo.create( UserInfo info = UserInfo.create(
miBandAddress, miBandAddress,
prefs.getString(MiBandConst.PREF_USER_ALIAS, null), prefs.getString(MiBandConst.PREF_USER_ALIAS, null),
("male".equals(prefs.getString(MiBandConst.PREF_USER_GENDER, null)) ? 1 : 0), activityUser.getActivityUserGender(),
age, activityUser.getActivityUserAge(),
Integer.parseInt(prefs.getString(MiBandConst.PREF_USER_HEIGHT_CM, "175")), activityUser.getActivityUserHeightCm(),
Integer.parseInt(prefs.getString(MiBandConst.PREF_USER_WEIGHT_KG, "70")), activityUser.getActivityUserWeightKg(),
0 0
); );
return info; return info;
@ -130,20 +133,25 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
public static int getWearLocation(String miBandAddress) throws IllegalArgumentException { public static int getWearLocation(String miBandAddress) throws IllegalArgumentException {
int location = 0; //left hand int location = 0; //left hand
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext()); Prefs prefs = GBApplication.getPrefs();
if ("right".equals(prefs.getString(MiBandConst.PREF_MIBAND_WEARSIDE, "left"))) { if ("right".equals(prefs.getString(MiBandConst.PREF_MIBAND_WEARSIDE, "left"))) {
location = 1; // right hand location = 1; // right hand
} }
return location; return location;
} }
public static boolean getHeartrateSleepSupport(String miBandAddress) throws IllegalArgumentException {
Prefs prefs = GBApplication.getPrefs();
return prefs.getBoolean(MiBandConst.PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION, false);
}
public static int getFitnessGoal(String miBandAddress) throws IllegalArgumentException { public static int getFitnessGoal(String miBandAddress) throws IllegalArgumentException {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext()); Prefs prefs = GBApplication.getPrefs();
return Integer.parseInt(prefs.getString(MiBandConst.PREF_MIBAND_FITNESS_GOAL, "10000")); return prefs.getInt(MiBandConst.PREF_MIBAND_FITNESS_GOAL, 10000);
} }
public static int getReservedAlarmSlots(String miBandAddress) throws IllegalArgumentException { public static int getReservedAlarmSlots(String miBandAddress) throws IllegalArgumentException {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext()); Prefs prefs = GBApplication.getPrefs();
return Integer.parseInt(prefs.getString(MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, "0")); return prefs.getInt(MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, 0);
} }
} }

View File

@ -1,8 +1,8 @@
package nodomain.freeyourgadget.gadgetbridge.devices.miband; package nodomain.freeyourgadget.gadgetbridge.devices.miband;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -10,22 +10,28 @@ import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.AbstractMiFirmwareInfo;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
/**
* Also see Mi1SFirmwareInfo.
*/
public class MiBandFWHelper { public class MiBandFWHelper {
private static final Logger LOG = LoggerFactory.getLogger(MiBandFWHelper.class); private static final Logger LOG = LoggerFactory.getLogger(MiBandFWHelper.class);
private final Uri uri; /**
private final ContentResolver cr; * The backing firmware info instance, which in general supports the provided
private byte[] fw; * given firmware. You must call AbstractMiFirmwareInfo#checkValid() before
* attempting to flash it.
private final int offsetFirmwareVersionBuild = 1056; */
private final int offsetFirmwareVersionRevision = 1057; @NonNull
private final int offsetFirmwareVersionMinor = 1058; private final AbstractMiFirmwareInfo firmwareInfo;
private final int offsetFirmwareVersionMajor = 1059; @NonNull
private final byte[] fw;
/** /**
* Provides a different notification API which is also used on Mi1A devices. * Provides a different notification API which is also used on Mi1A devices.
@ -45,54 +51,56 @@ public class MiBandFWHelper {
}; };
public MiBandFWHelper(Uri uri, Context context) throws IOException { public MiBandFWHelper(Uri uri, Context context) throws IOException {
this.uri = uri;
cr = context.getContentResolver();
if (cr == null) {
throw new IOException("No content resolver");
}
String pebblePattern = ".*\\.(pbw|pbz|pbl)"; String pebblePattern = ".*\\.(pbw|pbz|pbl)";
if (uri.getPath().matches(pebblePattern)) { if (uri.getPath().matches(pebblePattern)) {
throw new IOException("Firmware has a filename that looks like a Pebble app/firmware."); throw new IOException("Firmware has a filename that looks like a Pebble app/firmware.");
} }
try (InputStream in = new BufferedInputStream(cr.openInputStream(uri))) { try (InputStream in = new BufferedInputStream(context.getContentResolver().openInputStream(uri))) {
this.fw = FileUtils.readAll(in, 1024 * 1024); // 1 MB this.fw = FileUtils.readAll(in, 1024 * 1024); // 1 MB
if (fw.length <= offsetFirmwareVersionMajor) { this.firmwareInfo = determineFirmwareInfoFor(fw);
throw new IOException("This doesn't seem to be a Mi Band firmware, file size too small.");
}
byte firmwareVersionMajor = fw[offsetFirmwareVersionMajor];
if (!isSupportedFirmwareVersionMajor(firmwareVersionMajor)) {
throw new IOException("Firmware major version not supported, either too new or this isn't a Mi Band firmware: " + firmwareVersionMajor);
}
} catch (IOException ex) { } catch (IOException ex) {
throw ex; // pass through throw ex; // pass through
} catch (IllegalArgumentException ex) {
throw new IOException("This doesn't seem to be a Mi Band firmware: " + ex.getLocalizedMessage(), ex);
} catch (Exception e) { } catch (Exception e) {
throw new IOException("Error reading firmware file: " + uri.toString(), e); throw new IOException("Error reading firmware file: " + uri.toString(), e);
} }
} }
private byte getFirmwareVersionMajor() {
return fw[offsetFirmwareVersionMajor];
}
private byte getFirmwareVersionMinor() {
return fw[offsetFirmwareVersionMinor];
}
private boolean isSupportedFirmwareVersionMajor(byte firmwareVersionMajor) {
return firmwareVersionMajor == 1 || firmwareVersionMajor == 5;
}
public int getFirmwareVersion() { public int getFirmwareVersion() {
return (fw[offsetFirmwareVersionMajor] << 24) | (fw[offsetFirmwareVersionMinor] << 16) | (fw[offsetFirmwareVersionRevision] << 8) | fw[offsetFirmwareVersionBuild]; // FIXME: UnsupportedOperationException!
return firmwareInfo.getFirst().getFirmwareVersion();
}
public int getFirmware2Version() {
return firmwareInfo.getFirst().getFirmwareVersion();
}
public static String formatFirmwareVersion(int version) {
if (version == -1)
return GBApplication.getContext().getString(R.string._unknown_);
return String.format("%d.%d.%d.%d",
version >> 24 & 255,
version >> 16 & 255,
version >> 8 & 255,
version & 255);
} }
public String getHumanFirmwareVersion() { public String getHumanFirmwareVersion() {
return String.format(Locale.US, "%d.%d.%d.%d", fw[offsetFirmwareVersionMajor], fw[offsetFirmwareVersionMinor], fw[offsetFirmwareVersionRevision], fw[offsetFirmwareVersionBuild]); return format(getFirmwareVersion());
} }
public String getHumanFirmwareVersion2() {
return format(firmwareInfo.getSecond().getFirmwareVersion());
}
public String format(int version) {
return formatFirmwareVersion(version);
}
@NonNull
public byte[] getFw() { public byte[] getFw() {
return fw; return fw;
} }
@ -107,13 +115,31 @@ public class MiBandFWHelper {
} }
public boolean isFirmwareGenerallyCompatibleWith(GBDevice device) { public boolean isFirmwareGenerallyCompatibleWith(GBDevice device) {
String deviceHW = device.getHardwareVersion(); return firmwareInfo.isGenerallyCompatibleWith(device);
if (MiBandConst.MI_1.equals(deviceHW)) { }
return getFirmwareVersionMajor() == 1;
} public boolean isSingleFirmware() {
if (MiBandConst.MI_1A.equals(deviceHW)) { return firmwareInfo.isSingleMiBandFirmware();
return getFirmwareVersionMajor() == 5; }
}
return false; /**
* @param wholeFirmwareBytes
* @return
* @throws IllegalArgumentException when the data is not recognized as firmware data
*/
public static
@NonNull
AbstractMiFirmwareInfo determineFirmwareInfoFor(byte[] wholeFirmwareBytes) {
return AbstractMiFirmwareInfo.determineFirmwareInfoFor(wholeFirmwareBytes);
}
/**
* The backing firmware info instance, which in general supports the provided
* given firmware. You MUST call AbstractMiFirmwareInfo#checkValid() AND
* isGenerallyCompatibleWithDevice() before attempting to flash it.
*/
@NonNull
public AbstractMiFirmwareInfo getFirmwareInfo() {
return firmwareInfo;
} }
} }

View File

@ -47,6 +47,14 @@ public class MiBandFWInstallHandler implements InstallHandler {
return; return;
} }
try {
helper.getFirmwareInfo().checkValid();
} catch (IllegalArgumentException ex) {
installActivity.setInfoText(ex.getLocalizedMessage());
installActivity.setInstallEnabled(false);
return;
}
GenericItem fwItem = new GenericItem(mContext.getString(R.string.miband_installhandler_miband_firmware, helper.getHumanFirmwareVersion())); GenericItem fwItem = new GenericItem(mContext.getString(R.string.miband_installhandler_miband_firmware, helper.getHumanFirmwareVersion()));
fwItem.setIcon(R.drawable.ic_device_miband); fwItem.setIcon(R.drawable.ic_device_miband);
@ -56,7 +64,13 @@ public class MiBandFWInstallHandler implements InstallHandler {
installActivity.setInstallEnabled(false); installActivity.setInstallEnabled(false);
return; return;
} }
StringBuilder builder = new StringBuilder(mContext.getString(R.string.fw_upgrade_notice, helper.getHumanFirmwareVersion())); StringBuilder builder = new StringBuilder();
if (helper.isSingleFirmware()) {
builder.append(mContext.getString(R.string.fw_upgrade_notice, helper.getHumanFirmwareVersion()));
} else {
builder.append(mContext.getString(R.string.fw_multi_upgrade_notice, helper.getHumanFirmwareVersion(), helper.getHumanFirmwareVersion2()));
}
if (helper.isFirmwareWhitelisted()) { if (helper.isFirmwareWhitelisted()) {
builder.append(" ").append(mContext.getString(R.string.miband_firmware_known)); builder.append(" ").append(mContext.getString(R.string.miband_firmware_known));

View File

@ -7,11 +7,9 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -26,6 +24,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.DiscoveryActivity;
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.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class MiBandPairingActivity extends Activity { public class MiBandPairingActivity extends Activity {
private static final Logger LOG = LoggerFactory.getLogger(MiBandPairingActivity.class); private static final Logger LOG = LoggerFactory.getLogger(MiBandPairingActivity.class);
@ -159,13 +158,19 @@ public class MiBandPairingActivity extends Activity {
} }
private void pairingFinished(boolean pairedSuccessfully) { private void pairingFinished(boolean pairedSuccessfully) {
LOG.debug("pairingFinished: " + pairedSuccessfully);
if (!isPairing) {
// already gone?
return;
}
isPairing = false; isPairing = false;
LocalBroadcastManager.getInstance(this).unregisterReceiver(mPairingReceiver); LocalBroadcastManager.getInstance(this).unregisterReceiver(mPairingReceiver);
unregisterReceiver(mBondingReceiver); unregisterReceiver(mBondingReceiver);
if (pairedSuccessfully) { if (pairedSuccessfully) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); Prefs prefs = GBApplication.getPrefs();
sharedPrefs.edit().putString(MiBandConst.PREF_MIBAND_ADDRESS, macAddress).apply(); prefs.getPreferences().edit().putString(MiBandConst.PREF_MIBAND_ADDRESS, macAddress).apply();
} }
Intent intent = new Intent(this, ControlCenter.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); Intent intent = new Intent(this, ControlCenter.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
@ -188,12 +193,13 @@ public class MiBandPairingActivity extends Activity {
bondingMacAddress = device.getAddress(); bondingMacAddress = device.getAddress();
if (bondState == BluetoothDevice.BOND_BONDING) { if (bondState == BluetoothDevice.BOND_BONDING) {
LOG.info("Bonding in progress: " + device.getAddress()); GB.toast(this, "Bonding in progress: " + bondingMacAddress, Toast.LENGTH_LONG, GB.INFO);
return; return;
} }
GB.toast(this, "Creating bond with" + bondingMacAddress, Toast.LENGTH_LONG, GB.INFO);
if (!device.createBond()) { if (!device.createBond()) {
GB.toast(this, "Unable to pair with " + device.getAddress(), Toast.LENGTH_LONG, GB.ERROR); GB.toast(this, "Unable to pair with " + bondingMacAddress, Toast.LENGTH_LONG, GB.ERROR);
} }
} }

View File

@ -5,6 +5,7 @@ import android.os.Bundle;
import android.preference.Preference; import android.preference.Preference;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter; import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
@ -18,12 +19,9 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PR
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DONT_ACK_TRANSFER; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DONT_ACK_TRANSFER;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_FITNESS_GOAL; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_FITNESS_GOAL;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_WEARSIDE; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_WEARSIDE;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_ALIAS; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_ALIAS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_GENDER;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_HEIGHT_CM;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_WEIGHT_KG;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_YEAR_OF_BIRTH;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_COUNT; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_COUNT;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_PROFILE; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_PROFILE;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefKey; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefKey;
@ -47,30 +45,27 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
}); });
final Preference enableHeartrateSleepSupport = findPreference(PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION);
enableHeartrateSleepSupport.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
GBApplication.deviceService().onEnableHeartRateSleepSupport(Boolean.TRUE.equals(newVal));
return true;
}
});
} }
@Override @Override
protected String[] getPreferenceKeysWithSummary() { protected String[] getPreferenceKeysWithSummary() {
return new String[]{ return new String[]{
PREF_USER_ALIAS, PREF_USER_ALIAS,
PREF_USER_YEAR_OF_BIRTH,
PREF_USER_GENDER,
PREF_USER_HEIGHT_CM,
PREF_USER_WEIGHT_KG,
PREF_MIBAND_WEARSIDE,
PREF_MIBAND_ADDRESS, PREF_MIBAND_ADDRESS,
PREF_MIBAND_FITNESS_GOAL, PREF_MIBAND_FITNESS_GOAL,
PREF_MIBAND_DONT_ACK_TRANSFER,
PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR,
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_SMS),
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_SMS), getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_SMS),
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_INCOMING_CALL),
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_INCOMING_CALL), getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_INCOMING_CALL),
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_K9MAIL),
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_K9MAIL), getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_K9MAIL),
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_PEBBLEMSG),
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_PEBBLEMSG), getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_PEBBLEMSG),
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_GENERIC),
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_GENERIC), getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_GENERIC),
}; };
} }

View File

@ -4,12 +4,12 @@ import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class MiBandSampleProvider implements SampleProvider { public class MiBandSampleProvider implements SampleProvider {
public static final byte TYPE_DEEP_SLEEP = 5; public static final int TYPE_DEEP_SLEEP = 5;
public static final byte TYPE_LIGHT_SLEEP = 4; public static final int TYPE_LIGHT_SLEEP = 4;
public static final byte TYPE_ACTIVITY = -1; public static final int TYPE_ACTIVITY = -1;
public static final byte TYPE_UNKNOWN = -1; public static final int TYPE_UNKNOWN = -1;
public static final byte TYPE_NONWEAR = 3; public static final int TYPE_NONWEAR = 3;
public static final byte TYPE_CHARGING = 6; public static final int TYPE_CHARGING = 6;
// public static final byte TYPE_NREM = 5; // DEEP SLEEP // public static final byte TYPE_NREM = 5; // DEEP SLEEP
// public static final byte TYPE_ONBED = 7; // public static final byte TYPE_ONBED = 7;
@ -23,7 +23,7 @@ public class MiBandSampleProvider implements SampleProvider {
private final float movementDivisor = 180.0f; //256.0f; private final float movementDivisor = 180.0f; //256.0f;
@Override @Override
public int normalizeType(byte rawType) { public int normalizeType(int rawType) {
switch (rawType) { switch (rawType) {
case TYPE_DEEP_SLEEP: case TYPE_DEEP_SLEEP:
return ActivityKind.TYPE_DEEP_SLEEP; return ActivityKind.TYPE_DEEP_SLEEP;
@ -42,7 +42,7 @@ public class MiBandSampleProvider implements SampleProvider {
} }
@Override @Override
public byte toRawActivityKind(int activityKind) { public int toRawActivityKind(int activityKind) {
switch (activityKind) { switch (activityKind) {
case ActivityKind.TYPE_ACTIVITY: case ActivityKind.TYPE_ACTIVITY:
return TYPE_ACTIVITY; return TYPE_ACTIVITY;
@ -59,12 +59,12 @@ public class MiBandSampleProvider implements SampleProvider {
} }
@Override @Override
public float normalizeIntensity(short rawIntensity) { public float normalizeIntensity(int rawIntensity) {
return rawIntensity / movementDivisor; return rawIntensity / movementDivisor;
} }
@Override @Override
public byte getID() { public int getID() {
return SampleProvider.PROVIDER_MIBAND; return SampleProvider.PROVIDER_MIBAND;
} }
} }

View File

@ -14,6 +14,8 @@ public class MiBandService {
public static final UUID UUID_SERVICE_MIBAND_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE0")); public static final UUID UUID_SERVICE_MIBAND_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE0"));
public static final UUID UUID_SERVICE_HEART_RATE = UUID.fromString(String.format(BASE_UUID, "180D"));
public static final UUID UUID_CHARACTERISTIC_DEVICE_INFO = UUID.fromString(String.format(BASE_UUID, "FF01")); public static final UUID UUID_CHARACTERISTIC_DEVICE_INFO = UUID.fromString(String.format(BASE_UUID, "FF01"));
public static final UUID UUID_CHARACTERISTIC_DEVICE_NAME = UUID.fromString(String.format(BASE_UUID, "FF02")); public static final UUID UUID_CHARACTERISTIC_DEVICE_NAME = UUID.fromString(String.format(BASE_UUID, "FF02"));
@ -44,6 +46,11 @@ public class MiBandService {
public static final UUID UUID_CHARACTERISTIC_PAIR = UUID.fromString(String.format(BASE_UUID, "FF0F")); public static final UUID UUID_CHARACTERISTIC_PAIR = UUID.fromString(String.format(BASE_UUID, "FF0F"));
public static final UUID UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT = UUID.fromString(String.format(BASE_UUID, "2A39"));
public static final UUID UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT = UUID.fromString(String.format(BASE_UUID, "2A37"));
/* FURTHER UUIDS that were mixed with the other params below. The base UUID for these is unknown */ /* FURTHER UUIDS that were mixed with the other params below. The base UUID for these is unknown */
public static final String UUID_SERVICE_WEIGHT_SERVICE = "00001530-0000-3512-2118-0009af100700"; public static final String UUID_SERVICE_WEIGHT_SERVICE = "00001530-0000-3512-2118-0009af100700";
@ -153,6 +160,12 @@ public class MiBandService {
public static final byte COMMAND_SET_REALTIME_STEP = 0x10; public static final byte COMMAND_SET_REALTIME_STEP = 0x10;
// Test HR
public static final byte COMMAND_SET_HR_SLEEP = 0x0;
public static final byte COMMAND_SET__HR_CONTINUOUS = 0x1;
public static final byte COMMAND_SET_HR_MANUAL = 0x2;
/* FURTHER COMMANDS: unchecked therefore left commented /* FURTHER COMMANDS: unchecked therefore left commented
@ -213,6 +226,7 @@ public class MiBandService {
static { static {
MIBAND_DEBUG = new HashMap<>(); MIBAND_DEBUG = new HashMap<>();
MIBAND_DEBUG.put(UUID_SERVICE_MIBAND_SERVICE, "MiBand Service"); MIBAND_DEBUG.put(UUID_SERVICE_MIBAND_SERVICE, "MiBand Service");
MIBAND_DEBUG.put(UUID_SERVICE_HEART_RATE, "MiBand HR Service");
MIBAND_DEBUG.put(UUID_CHARACTERISTIC_DEVICE_INFO, "Device Info"); MIBAND_DEBUG.put(UUID_CHARACTERISTIC_DEVICE_INFO, "Device Info");
MIBAND_DEBUG.put(UUID_CHARACTERISTIC_DEVICE_NAME, "Device Name"); MIBAND_DEBUG.put(UUID_CHARACTERISTIC_DEVICE_NAME, "Device Name");
@ -229,6 +243,8 @@ public class MiBandService {
MIBAND_DEBUG.put(UUID_CHARACTERISTIC_TEST, "Test"); MIBAND_DEBUG.put(UUID_CHARACTERISTIC_TEST, "Test");
MIBAND_DEBUG.put(UUID_CHARACTERISTIC_SENSOR_DATA, "Sensor Data"); MIBAND_DEBUG.put(UUID_CHARACTERISTIC_SENSOR_DATA, "Sensor Data");
MIBAND_DEBUG.put(UUID_CHARACTERISTIC_PAIR, "Pair"); MIBAND_DEBUG.put(UUID_CHARACTERISTIC_PAIR, "Pair");
MIBAND_DEBUG.put(UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT, "Heart Rate Control Point");
MIBAND_DEBUG.put(UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT, "Heart Rate Measure");
} }
public static String lookup(UUID uuid, String fallback) { public static String lookup(UUID uuid, String fallback) {

View File

@ -1,10 +1,11 @@
package nodomain.freeyourgadget.gadgetbridge.devices.miband; package nodomain.freeyourgadget.gadgetbridge.devices.miband;
import java.util.Arrays;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.DeviceInfo; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.DeviceInfo;
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
import java.util.Arrays;
public class UserInfo { public class UserInfo {
private final String btAddress; private final String btAddress;
@ -23,7 +24,7 @@ public class UserInfo {
* @param btAddress the address of the MI Band to connect to. * @param btAddress the address of the MI Band to connect to.
*/ */
public static UserInfo getDefault(String btAddress) { public static UserInfo getDefault(String btAddress) {
return new UserInfo(btAddress, "1550050550", 0, 25, 175, 70, 0); return new UserInfo(btAddress, "1550050550", ActivityUser.defaultUserGender, ActivityUser.defaultUserAge, ActivityUser.defaultUserHeightCm, ActivityUser.defaultUserWeightKg, 0);
} }
/** /**
@ -84,13 +85,13 @@ public class UserInfo {
sequence[8] = (byte) (type & 0xff); sequence[8] = (byte) (type & 0xff);
int aliasFrom = 9; int aliasFrom = 9;
if (mDeviceInfo.isMili1A() || mDeviceInfo.isMilli1S()) { if (!mDeviceInfo.isMili1()) {
sequence[9] = (byte) (mDeviceInfo.feature & 255); sequence[9] = (byte) (mDeviceInfo.feature & 255);
sequence[10] = (byte) (mDeviceInfo.appearance & 255); sequence[10] = (byte) (mDeviceInfo.appearance & 255);
aliasFrom = 11; aliasFrom = 11;
} }
byte[] aliasBytes = alias.substring(0, Math.min(alias.length(), 19-aliasFrom)).getBytes(); byte[] aliasBytes = alias.substring(0, Math.min(alias.length(), 19 - aliasFrom)).getBytes();
System.arraycopy(aliasBytes, 0, sequence, aliasFrom, aliasBytes.length); System.arraycopy(aliasBytes, 0, sequence, aliasFrom, aliasBytes.length);
byte[] crcSequence = Arrays.copyOf(sequence, 19); byte[] crcSequence = Arrays.copyOf(sequence, 19);

View File

@ -0,0 +1,52 @@
package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class HealthSampleProvider implements SampleProvider {
public static final int TYPE_DEEP_SLEEP = 5;
public static final int TYPE_LIGHT_SLEEP = 4;
public static final int TYPE_ACTIVITY = -1;
protected final float movementDivisor = 8000f;
@Override
public int normalizeType(int rawType) {
switch (rawType) {
case TYPE_DEEP_SLEEP:
return ActivityKind.TYPE_DEEP_SLEEP;
case TYPE_LIGHT_SLEEP:
return ActivityKind.TYPE_LIGHT_SLEEP;
case TYPE_ACTIVITY:
default:
return ActivityKind.TYPE_UNKNOWN;
}
}
@Override
public int toRawActivityKind(int activityKind) {
switch (activityKind) {
case ActivityKind.TYPE_ACTIVITY:
return TYPE_ACTIVITY;
case ActivityKind.TYPE_DEEP_SLEEP:
return TYPE_DEEP_SLEEP;
case ActivityKind.TYPE_LIGHT_SLEEP:
return TYPE_LIGHT_SLEEP;
case ActivityKind.TYPE_UNKNOWN: // fall through
default:
return TYPE_ACTIVITY;
}
}
@Override
public float normalizeIntensity(int rawIntensity) {
return rawIntensity / movementDivisor;
}
@Override
public int getID() {
return SampleProvider.PROVIDER_PEBBLE_HEALTH;
}
}

View File

@ -7,24 +7,24 @@ public class MisfitSampleProvider implements SampleProvider {
protected final float movementDivisor = 300f; protected final float movementDivisor = 300f;
@Override @Override
public int normalizeType(byte rawType) { public int normalizeType(int rawType) {
return (int) rawType; return (int) rawType;
} }
@Override @Override
public byte toRawActivityKind(int activityKind) { public int toRawActivityKind(int activityKind) {
return (byte) activityKind; return (byte) activityKind;
} }
@Override @Override
public float normalizeIntensity(short rawIntensity) { public float normalizeIntensity(int rawIntensity) {
return rawIntensity / movementDivisor; return rawIntensity / movementDivisor;
} }
@Override @Override
public byte getID() { public int getID() {
return SampleProvider.PROVIDER_PEBBLE_MISFIT; return SampleProvider.PROVIDER_PEBBLE_MISFIT;
} }
} }

View File

@ -5,15 +5,15 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class MorpheuzSampleProvider implements SampleProvider { public class MorpheuzSampleProvider implements SampleProvider {
// raw types // raw types
public static final byte TYPE_DEEP_SLEEP = 5; public static final int TYPE_DEEP_SLEEP = 5;
public static final byte TYPE_LIGHT_SLEEP = 4; public static final int TYPE_LIGHT_SLEEP = 4;
public static final byte TYPE_ACTIVITY = -1; public static final int TYPE_ACTIVITY = -1;
public static final byte TYPE_UNKNOWN = -1; public static final int TYPE_UNKNOWN = -1;
protected float movementDivisor = 5000f; protected float movementDivisor = 5000f;
@Override @Override
public int normalizeType(byte rawType) { public int normalizeType(int rawType) {
switch (rawType) { switch (rawType) {
case TYPE_DEEP_SLEEP: case TYPE_DEEP_SLEEP:
return ActivityKind.TYPE_DEEP_SLEEP; return ActivityKind.TYPE_DEEP_SLEEP;
@ -28,7 +28,7 @@ public class MorpheuzSampleProvider implements SampleProvider {
} }
@Override @Override
public byte toRawActivityKind(int activityKind) { public int toRawActivityKind(int activityKind) {
switch (activityKind) { switch (activityKind) {
case ActivityKind.TYPE_ACTIVITY: case ActivityKind.TYPE_ACTIVITY:
return TYPE_ACTIVITY; return TYPE_ACTIVITY;
@ -43,12 +43,12 @@ public class MorpheuzSampleProvider implements SampleProvider {
} }
@Override @Override
public float normalizeIntensity(short rawIntensity) { public float normalizeIntensity(int rawIntensity) {
return rawIntensity / movementDivisor; return rawIntensity / movementDivisor;
} }
@Override @Override
public byte getID() { public int getID() {
return SampleProvider.PROVIDER_PEBBLE_MORPHEUZ; return SampleProvider.PROVIDER_PEBBLE_MORPHEUZ;
} }
} }

View File

@ -3,6 +3,8 @@ package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -11,6 +13,7 @@ import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.Writer; import java.io.Writer;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
@ -21,6 +24,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
public class PBWInstallHandler implements InstallHandler { public class PBWInstallHandler implements InstallHandler {
private static final Logger LOG = LoggerFactory.getLogger(PBWInstallHandler.class); private static final Logger LOG = LoggerFactory.getLogger(PBWInstallHandler.class);
@ -47,15 +51,7 @@ public class PBWInstallHandler implements InstallHandler {
return; return;
} }
String hwRev = device.getHardwareVersion(); String platformName = PebbleUtils.getPlatformName(device.getHardwareVersion());
String platformName;
if (hwRev.startsWith("snowy")) {
platformName = "basalt";
} else if (hwRev.startsWith("spalding")) {
platformName = "chalk";
} else {
platformName = "aplite";
}
try { try {
mPBWReader = new PBWReader(mUri, mContext, platformName); mPBWReader = new PBWReader(mUri, mContext, platformName);
@ -158,10 +154,29 @@ public class PBWInstallHandler implements InstallHandler {
} }
try { try {
LOG.info(app.getJSON().toString()); LOG.info(app.getJSON().toString());
writer.write(app.getJSON().toString()); JSONObject appJSON = app.getJSON();
JSONObject appKeysJSON = mPBWReader.getAppKeysJSON();
if (appKeysJSON != null) {
appJSON.put("appKeys", appKeysJSON);
}
writer.write(appJSON.toString());
writer.close(); writer.close();
} catch (IOException e) { } catch (IOException e) {
LOG.error("Failed to write to output file: " + e.getMessage(), e); LOG.error("Failed to write to output file: " + e.getMessage(), e);
} catch (JSONException e) {
LOG.error(e.getMessage(), e);
}
InputStream jsConfigFile = mPBWReader.getInputStreamFile("pebble-js-app.js");
if (jsConfigFile != null) {
outputFile = new File(destDir, app.getUUID().toString() + "_config.js");
try {
FileUtils.copyStreamToFile(jsConfigFile, outputFile);
} catch (IOException e) {
LOG.error("Failed to open output file: " + e.getMessage(), e);
}
} }
} }

View File

@ -58,6 +58,8 @@ public class PBWReader {
private int mIconId; private int mIconId;
private int mFlags; private int mFlags;
private JSONObject mAppKeys = null;
public PBWReader(Uri uri, Context context, String platform) throws FileNotFoundException { public PBWReader(Uri uri, Context context, String platform) throws FileNotFoundException {
this.uri = uri; this.uri = uri;
cr = context.getContentResolver(); cr = context.getContentResolver();
@ -201,6 +203,10 @@ public class PBWReader {
appCreator = json.getString("companyName"); appCreator = json.getString("companyName");
appVersion = json.getString("versionLabel"); appVersion = json.getString("versionLabel");
appUUID = UUID.fromString(json.getString("uuid")); appUUID = UUID.fromString(json.getString("uuid"));
if (json.has("appKeys")) {
mAppKeys = json.getJSONObject("appKeys");
LOG.info("found appKeys:" + mAppKeys.toString());
}
} catch (JSONException e) { } catch (JSONException e) {
isValid = false; isValid = false;
e.printStackTrace(); e.printStackTrace();
@ -317,4 +323,8 @@ public class PBWReader {
public int getIconId() { public int getIconId() {
return mIconId; return mIconId;
} }
public JSONObject getAppKeysJSON() {
return mAppKeys;
}
} }

View File

@ -2,11 +2,10 @@ package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.preference.PreferenceManager;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AppManagerActivity; import nodomain.freeyourgadget.gadgetbridge.activities.AppManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
@ -14,6 +13,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class PebbleCoordinator extends AbstractDeviceCoordinator { public class PebbleCoordinator extends AbstractDeviceCoordinator {
public PebbleCoordinator() { public PebbleCoordinator() {
@ -45,13 +45,19 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
@Override @Override
public SampleProvider getSampleProvider() { public SampleProvider getSampleProvider() {
// FIXME: make this configurable somewhere else. Prefs prefs = GBApplication.getPrefs();
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext()); int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH);
if (sharedPrefs.getBoolean("pebble_force_untested", false)) { switch (activityTracker) {
//return new PebbleGadgetBridgeSampleProvider(); case SampleProvider.PROVIDER_PEBBLE_HEALTH:
return new MisfitSampleProvider(); return new HealthSampleProvider();
} else { case SampleProvider.PROVIDER_PEBBLE_MISFIT:
return new MorpheuzSampleProvider(); return new MisfitSampleProvider();
case SampleProvider.PROVIDER_PEBBLE_MORPHEUZ:
return new MorpheuzSampleProvider();
case SampleProvider.PROVIDER_PEBBLE_GADGETBRIDGE:
return new PebbleGadgetBridgeSampleProvider();
default:
return new HealthSampleProvider();
} }
} }
@ -70,4 +76,14 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
public boolean supportsScreenshots() { public boolean supportsScreenshots() {
return true; return true;
} }
@Override
public boolean supportsAlarmConfiguration() {
return false;
}
@Override
public int getTapString() {
return R.string.tap_connected_device_for_app_mananger;
}
} }

View File

@ -8,7 +8,7 @@ public class PebbleGadgetBridgeSampleProvider extends MorpheuzSampleProvider {
} }
@Override @Override
public byte getID() { public int getID() {
return SampleProvider.PROVIDER_PEBBLE_GADGETBRIDGE; return SampleProvider.PROVIDER_PEBBLE_GADGETBRIDGE;
} }
} }

View File

@ -4,12 +4,11 @@ import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter; import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class BluetoothStateChangeReceiver extends BroadcastReceiver { public class BluetoothStateChangeReceiver extends BroadcastReceiver {
@Override @Override
@ -22,8 +21,8 @@ public class BluetoothStateChangeReceiver extends BroadcastReceiver {
Intent refreshIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST); Intent refreshIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent); LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); Prefs prefs = GBApplication.getPrefs();
if (!sharedPrefs.getBoolean("general_autoconnectonbluetooth", false)) { if (!prefs.getBoolean("general_autoconnectonbluetooth", false)) {
return; return;
} }

View File

@ -3,11 +3,9 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.PowerManager; import android.os.PowerManager;
import android.preference.PreferenceManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -15,6 +13,7 @@ import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
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.util.Prefs;
public class K9Receiver extends BroadcastReceiver { public class K9Receiver extends BroadcastReceiver {
@ -24,11 +23,11 @@ public class K9Receiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); Prefs prefs = GBApplication.getPrefs();
if ("never".equals(sharedPrefs.getString("notification_mode_k9mail", "when_screen_off"))) { if ("never".equals(prefs.getString("notification_mode_k9mail", "when_screen_off"))) {
return; return;
} }
if ("when_screen_off".equals(sharedPrefs.getString("notification_mode_k9mail", "when_screen_off"))) { if ("when_screen_off".equals(prefs.getString("notification_mode_k9mail", "when_screen_off"))) {
PowerManager powermanager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); PowerManager powermanager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (powermanager.isScreenOn()) { if (powermanager.isScreenOn()) {
return; return;
@ -55,20 +54,9 @@ public class K9Receiver extends BroadcastReceiver {
* It should be the first one returned by the query in most cases, * It should be the first one returned by the query in most cases,
*/ */
Cursor c = null; try (Cursor c = context.getContentResolver().query(k9Uri, messagesProjection, null, null, null)) {
try {
c = context.getContentResolver().query(k9Uri, messagesProjection, null, null, null);
} catch (Exception e) {
e.printStackTrace();
notificationSpec.sender = "Gadgetbridge";
notificationSpec.subject = "Permission Error?";
notificationSpec.body = "Please reinstall Gadgerbridge to enable K-9 Mail notifications";
}
try {
if (c != null) { if (c != null) {
c.moveToFirst(); while (c.moveToNext()) {
do {
String uri = c.getString(c.getColumnIndex("uri")); String uri = c.getString(c.getColumnIndex("uri"));
if (uri.equals(uriWanted)) { if (uri.equals(uriWanted)) {
notificationSpec.sender = c.getString(c.getColumnIndex("senderAddress")); notificationSpec.sender = c.getString(c.getColumnIndex("senderAddress"));
@ -76,12 +64,13 @@ public class K9Receiver extends BroadcastReceiver {
notificationSpec.body = c.getString(c.getColumnIndex("preview")); notificationSpec.body = c.getString(c.getColumnIndex("preview"));
break; break;
} }
} while (c.moveToNext()); }
}
} finally {
if (c != null) {
c.close();
} }
} catch (Exception e) {
e.printStackTrace();
notificationSpec.sender = "Gadgetbridge";
notificationSpec.subject = "Permission Error?";
notificationSpec.body = "Please reinstall Gadgetbridge to enable K-9 Mail notifications";
} }
GBApplication.deviceService().onNotification(notificationSpec); GBApplication.deviceService().onNotification(notificationSpec);

View File

@ -8,20 +8,31 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
public class MusicPlaybackReceiver extends BroadcastReceiver { public class MusicPlaybackReceiver extends BroadcastReceiver {
private static final Logger LOG = LoggerFactory.getLogger(MusicPlaybackReceiver.class); private static final Logger LOG = LoggerFactory.getLogger(MusicPlaybackReceiver.class);
private static String mLastSource;
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String artist = intent.getStringExtra("artist"); String artist = intent.getStringExtra("artist");
String album = intent.getStringExtra("album"); String album = intent.getStringExtra("album");
String track = intent.getStringExtra("track"); String track = intent.getStringExtra("track");
/*
Bundle bundle = intent.getExtras();
for (String key : bundle.keySet()) {
Object value = bundle.get(key);
LOG.info(String.format("%s %s (%s)", key,
value != null ? value.toString() : "null", value != null ? value.getClass().getName() : "no class"));
}
*/
LOG.info("Current track: " + artist + ", " + album + ", " + track); LOG.info("Current track: " + artist + ", " + album + ", " + track);
GBApplication.deviceService().onSetMusicInfo(artist, album, track); MusicSpec musicSpec = new MusicSpec();
musicSpec.artist = artist;
musicSpec.artist = album;
musicSpec.artist = track;
GBApplication.deviceService().onSetMusicInfo(musicSpec);
} }
} }

View File

@ -8,12 +8,10 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.PowerManager; import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
@ -30,6 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class NotificationListener extends NotificationListenerService { public class NotificationListener extends NotificationListenerService {
@ -54,6 +53,9 @@ public class NotificationListener extends NotificationListenerService {
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 (action) {
case GBApplication.ACTION_QUIT:
stopSelf();
break;
case ACTION_MUTE: case ACTION_MUTE:
case ACTION_OPEN: { case ACTION_OPEN: {
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications(); StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
@ -130,6 +132,7 @@ public class NotificationListener extends NotificationListenerService {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
IntentFilter filterLocal = new IntentFilter(); IntentFilter filterLocal = new IntentFilter();
filterLocal.addAction(GBApplication.ACTION_QUIT);
filterLocal.addAction(ACTION_OPEN); filterLocal.addAction(ACTION_OPEN);
filterLocal.addAction(ACTION_DISMISS); filterLocal.addAction(ACTION_DISMISS);
filterLocal.addAction(ACTION_DISMISS_ALL); filterLocal.addAction(ACTION_DISMISS_ALL);
@ -157,8 +160,8 @@ public class NotificationListener extends NotificationListenerService {
return; return;
} }
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); Prefs prefs = GBApplication.getPrefs();
if (!sharedPrefs.getBoolean("notifications_generic_whenscreenon", false)) { if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE); PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
if (powermanager.isScreenOn()) { if (powermanager.isScreenOn()) {
return; return;
@ -185,13 +188,13 @@ public class NotificationListener extends NotificationListenerService {
} }
if (source.equals("eu.siacs.conversations")) { if (source.equals("eu.siacs.conversations")) {
if (!"never".equals(sharedPrefs.getString("notification_mode_pebblemsg", "when_screen_off"))) { if (!"never".equals(prefs.getString("notification_mode_pebblemsg", "when_screen_off"))) {
return; return;
} }
} }
if (source.equals("com.fsck.k9")) { if (source.equals("com.fsck.k9")) {
if (!"never".equals(sharedPrefs.getString("notification_mode_k9mail", "when_screen_off"))) { if (!"never".equals(prefs.getString("notification_mode_k9mail", "when_screen_off"))) {
return; return;
} }
} }
@ -201,7 +204,7 @@ public class NotificationListener extends NotificationListenerService {
source.equals("com.sonyericsson.conversations") || source.equals("com.sonyericsson.conversations") ||
source.equals("com.android.messaging") || source.equals("com.android.messaging") ||
source.equals("org.smssecure.smssecure")) { source.equals("org.smssecure.smssecure")) {
if (!"never".equals(sharedPrefs.getString("notification_mode_sms", "when_screen_off"))) { if (!"never".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) {
return; return;
} }
} }

View File

@ -3,9 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.os.PowerManager; import android.os.PowerManager;
import android.preference.PreferenceManager;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@ -15,6 +13,7 @@ import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
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.util.Prefs;
public class PebbleReceiver extends BroadcastReceiver { public class PebbleReceiver extends BroadcastReceiver {
@ -23,11 +22,11 @@ public class PebbleReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); Prefs prefs = GBApplication.getPrefs();
if ("never".equals(sharedPrefs.getString("notification_mode_pebblemsg", "when_screen_off"))) { if ("never".equals(prefs.getString("notification_mode_pebblemsg", "when_screen_off"))) {
return; return;
} }
if ("when_screen_off".equals(sharedPrefs.getString("notification_mode_pebblemsg", "when_screen_off"))) { if ("when_screen_off".equals(prefs.getString("notification_mode_pebblemsg", "when_screen_off"))) {
PowerManager powermanager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); PowerManager powermanager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (powermanager.isScreenOn()) { if (powermanager.isScreenOn()) {
return; return;

View File

@ -3,12 +3,11 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class PhoneCallReceiver extends BroadcastReceiver { public class PhoneCallReceiver extends BroadcastReceiver {
@ -31,7 +30,6 @@ public class PhoneCallReceiver extends BroadcastReceiver {
} else if (TelephonyManager.EXTRA_STATE_RINGING.equals(stateStr)) { } else if (TelephonyManager.EXTRA_STATE_RINGING.equals(stateStr)) {
state = TelephonyManager.CALL_STATE_RINGING; state = TelephonyManager.CALL_STATE_RINGING;
} }
onCallStateChanged(context, state, number); onCallStateChanged(context, state, number);
} }
} }
@ -41,34 +39,38 @@ public class PhoneCallReceiver extends BroadcastReceiver {
return; return;
} }
ServiceCommand callCommand = null; int callCommand = CallSpec.CALL_UNDEFINED;
switch (state) { switch (state) {
case TelephonyManager.CALL_STATE_RINGING: case TelephonyManager.CALL_STATE_RINGING:
mSavedNumber = number; mSavedNumber = number;
callCommand = ServiceCommand.CALL_INCOMING; callCommand = CallSpec.CALL_INCOMING;
break; break;
case TelephonyManager.CALL_STATE_OFFHOOK: case TelephonyManager.CALL_STATE_OFFHOOK:
if (mLastState == TelephonyManager.CALL_STATE_RINGING) { if (mLastState == TelephonyManager.CALL_STATE_RINGING) {
callCommand = ServiceCommand.CALL_START; callCommand = CallSpec.CALL_START;
} else { } else {
callCommand = ServiceCommand.CALL_OUTGOING; callCommand = CallSpec.CALL_OUTGOING;
mSavedNumber = number;
} }
break; break;
case TelephonyManager.CALL_STATE_IDLE: case TelephonyManager.CALL_STATE_IDLE:
if (mLastState == TelephonyManager.CALL_STATE_RINGING) { if (mLastState == TelephonyManager.CALL_STATE_RINGING) {
//missed call would be correct here //missed call would be correct here
callCommand = ServiceCommand.CALL_END; callCommand = CallSpec.CALL_END;
} else { } else {
callCommand = ServiceCommand.CALL_END; callCommand = CallSpec.CALL_END;
} }
break; break;
} }
if (callCommand != null) { if (callCommand != CallSpec.CALL_UNDEFINED) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); Prefs prefs = GBApplication.getPrefs();
if ("never".equals(sharedPrefs.getString("notification_mode_calls", "always"))) { if ("never".equals(prefs.getString("notification_mode_calls", "always"))) {
return; return;
} }
GBApplication.deviceService().onSetCallState(mSavedNumber, null, callCommand); CallSpec callSpec = new CallSpec();
callSpec.number = mSavedNumber;
callSpec.command = callCommand;
GBApplication.deviceService().onSetCallState(callSpec);
} }
mLastState = state; mLastState = state;
} }

View File

@ -3,28 +3,24 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.os.PowerManager; import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.telephony.SmsMessage; import android.telephony.SmsMessage;
import java.util.ArrayList;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
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.util.Prefs;
public class SMSReceiver extends BroadcastReceiver { public class SMSReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Prefs prefs = GBApplication.getPrefs();
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); if ("never".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) {
if ("never".equals(sharedPrefs.getString("notification_mode_sms", "when_screen_off"))) {
return; return;
} }
if ("when_screen_off".equals(sharedPrefs.getString("notification_mode_sms", "when_screen_off"))) { if ("when_screen_off".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) {
PowerManager powermanager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); PowerManager powermanager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (powermanager.isScreenOn()) { if (powermanager.isScreenOn()) {
return; return;

View File

@ -3,8 +3,6 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -14,6 +12,7 @@ import java.util.GregorianCalendar;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class TimeChangeReceiver extends BroadcastReceiver { public class TimeChangeReceiver extends BroadcastReceiver {
@ -22,10 +21,10 @@ public class TimeChangeReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); Prefs prefs = GBApplication.getPrefs();
final String action = intent.getAction(); final String action = intent.getAction();
if (sharedPrefs.getBoolean("datetime_synconconnect", true) && (action.equals(Intent.ACTION_TIME_CHANGED) || action.equals(Intent.ACTION_TIMEZONE_CHANGED))) { if (prefs.getBoolean("datetime_synconconnect", true) && (action.equals(Intent.ACTION_TIME_CHANGED) || action.equals(Intent.ACTION_TIMEZONE_CHANGED))) {
Date newTime = GregorianCalendar.getInstance().getTime(); Date newTime = GregorianCalendar.getInstance().getTime();
LOG.info("Time or Timezone changed, syncing with device: " + DateTimeUtils.formatDate(newTime) + " (" + newTime.toGMTString() + "), " + intent.getAction()); LOG.info("Time or Timezone changed, syncing with device: " + DateTimeUtils.formatDate(newTime) + " (" + newTime.toGMTString() + "), " + intent.getAction());
GBApplication.deviceService().onSetTime(); GBApplication.deviceService().onSetTime();

View File

@ -2,32 +2,42 @@ package nodomain.freeyourgadget.gadgetbridge.impl;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
public class GBActivitySample implements ActivitySample { public class GBActivitySample implements ActivitySample {
private final int timestamp; private final int timestamp;
private final SampleProvider provider; private final SampleProvider provider;
private final short intensity; private final int intensity;
private final short steps; private final int steps;
private final byte type; private final int type;
private final int customValue;
public GBActivitySample(SampleProvider provider, int timestamp, short intensity, short steps, byte type) { public GBActivitySample(SampleProvider provider, int timestamp, int intensity, int steps, int type) {
this(provider, timestamp, intensity, steps, type, 0);
}
public GBActivitySample(SampleProvider provider, int timestamp, int intensity, int steps, int type, int customValue) {
this.timestamp = timestamp; this.timestamp = timestamp;
this.provider = provider; this.provider = provider;
this.intensity = intensity; this.intensity = intensity;
this.steps = steps; this.steps = steps;
this.customValue = customValue;
this.type = type; this.type = type;
validate(); validate();
} }
private void validate() { private void validate() {
if (steps < 0) { if (steps < 0) {
throw new IllegalArgumentException("steps must be > 0"); throw new IllegalArgumentException("steps must be >= 0");
} }
if (intensity < 0) { if (intensity < 0) {
throw new IllegalArgumentException("intensity must be > 0"); throw new IllegalArgumentException("intensity must be >= 0");
} }
if (timestamp < 0) { if (timestamp < 0) {
throw new IllegalArgumentException("timestamp must be > 0"); throw new IllegalArgumentException("timestamp must be >= 0");
}
if (customValue < 0) {
throw new IllegalArgumentException("customValue must be >= 0");
} }
} }
@ -42,7 +52,7 @@ public class GBActivitySample implements ActivitySample {
} }
@Override @Override
public short getRawIntensity() { public int getRawIntensity() {
return intensity; return intensity;
} }
@ -52,12 +62,12 @@ public class GBActivitySample implements ActivitySample {
} }
@Override @Override
public short getSteps() { public int getSteps() {
return steps; return steps;
} }
@Override @Override
public byte getRawKind() { public int getRawKind() {
return type; return type;
} }
@ -65,4 +75,20 @@ public class GBActivitySample implements ActivitySample {
public int getKind() { public int getKind() {
return getProvider().normalizeType(getRawKind()); return getProvider().normalizeType(getRawKind());
} }
@Override
public int getCustomValue() {
return customValue;
}
@Override
public String toString() {
return "GBActivitySample{" +
"timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimeStamp(timestamp)) +
", intensity=" + getIntensity() +
", steps=" + getSteps() +
", customValue=" + getCustomValue() +
", type=" + getKind() +
'}';
}
} }

View File

@ -1,8 +1,6 @@
package nodomain.freeyourgadget.gadgetbridge.impl; package nodomain.freeyourgadget.gadgetbridge.impl;
import android.content.SharedPreferences;
import android.os.Parcel; import android.os.Parcel;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import java.util.Calendar; import java.util.Calendar;
@ -12,6 +10,7 @@ import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ALARMS; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ALARMS;
@ -187,8 +186,8 @@ public class GBAlarm implements Alarm {
} }
public void store() { public void store() {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext()); Prefs prefs = GBApplication.getPrefs();
Set<String> preferencesAlarmListSet = sharedPrefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>()); Set<String> preferencesAlarmListSet = prefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>());
//the old Set cannot be updated in place see http://developer.android.com/reference/android/content/SharedPreferences.html#getStringSet%28java.lang.String,%20java.util.Set%3Cjava.lang.String%3E%29 //the old Set cannot be updated in place see http://developer.android.com/reference/android/content/SharedPreferences.html#getStringSet%28java.lang.String,%20java.util.Set%3Cjava.lang.String%3E%29
Set<String> newPrefs = new HashSet<>(preferencesAlarmListSet); Set<String> newPrefs = new HashSet<>(preferencesAlarmListSet);
@ -202,7 +201,7 @@ public class GBAlarm implements Alarm {
} }
} }
newPrefs.add(this.toPreferences()); newPrefs.add(this.toPreferences());
sharedPrefs.edit().putStringSet(PREF_MIBAND_ALARMS, newPrefs).apply(); prefs.getPreferences().edit().putStringSet(PREF_MIBAND_ALARMS, newPrefs).apply();
} }
public static final Creator CREATOR = new Creator() { public static final Creator CREATOR = new Creator() {

View File

@ -10,10 +10,16 @@ import android.support.v4.content.LocalBroadcastManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
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.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.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
public class GBDevice implements Parcelable { public class GBDevice implements Parcelable {
public static final String ACTION_DEVICE_CHANGED public static final String ACTION_DEVICE_CHANGED
@ -34,6 +40,8 @@ public class GBDevice implements Parcelable {
public static final short BATTERY_UNKNOWN = -1; public static final short BATTERY_UNKNOWN = -1;
private static final short BATTERY_THRESHOLD_PERCENT = 10; private static final short BATTERY_THRESHOLD_PERCENT = 10;
public static final String EXTRA_DEVICE = "device"; public static final String EXTRA_DEVICE = "device";
private static final String DEVINFO_HW_VER = "HW: ";
private static final String DEVINFO_FW_VER = "FW: ";
private final String mName; private final String mName;
private final String mAddress; private final String mAddress;
private final DeviceType mDeviceType; private final DeviceType mDeviceType;
@ -45,6 +53,7 @@ public class GBDevice implements Parcelable {
private BatteryState mBatteryState; private BatteryState mBatteryState;
private short mRssi = RSSI_UNKNOWN; private short mRssi = RSSI_UNKNOWN;
private String mBusyTask; private String mBusyTask;
private List<ItemWithDetails> mDeviceInfos;
public GBDevice(String address, String name, DeviceType deviceType) { public GBDevice(String address, String name, DeviceType deviceType) {
mAddress = address; mAddress = address;
@ -65,6 +74,7 @@ public class GBDevice implements Parcelable {
mBatteryState = (BatteryState) in.readSerializable(); mBatteryState = (BatteryState) in.readSerializable();
mRssi = (short) in.readInt(); mRssi = (short) in.readInt();
mBusyTask = in.readString(); mBusyTask = in.readString();
mDeviceInfos = in.readArrayList(getClass().getClassLoader());
validate(); validate();
} }
@ -82,6 +92,7 @@ public class GBDevice implements Parcelable {
dest.writeSerializable(mBatteryState); dest.writeSerializable(mBatteryState);
dest.writeInt(mRssi); dest.writeInt(mRssi);
dest.writeString(mBusyTask); dest.writeString(mBusyTask);
dest.writeList(mDeviceInfos);
} }
private void validate() { private void validate() {
@ -192,35 +203,29 @@ public class GBDevice implements Parcelable {
} }
public String getStateString() { public String getStateString() {
/*
* for simplicity the user wont see all internal states, just connecting -> connected
* instead of connecting->connected->initializing->initialized
*/
switch (mState) { switch (mState) {
case NOT_CONNECTED: case NOT_CONNECTED:
return GBApplication.getContext().getString(R.string.not_connected); return GBApplication.getContext().getString(R.string.not_connected);
case WAITING_FOR_RECONNECT: case WAITING_FOR_RECONNECT:
return GBApplication.getContext().getString(R.string.waiting_for_reconnect); return GBApplication.getContext().getString(R.string.waiting_for_reconnect);
case CONNECTING: case CONNECTING:
return GBApplication.getContext().getString(R.string.connecting);
case CONNECTED: case CONNECTED:
return GBApplication.getContext().getString(R.string.connected);
case INITIALIZING: case INITIALIZING:
return GBApplication.getContext().getString(R.string.initializing); return GBApplication.getContext().getString(R.string.connecting);
case AUTHENTICATION_REQUIRED:
return GBApplication.getContext().getString(R.string.authentication_required);
case AUTHENTICATING:
return GBApplication.getContext().getString(R.string.authenticating);
case INITIALIZED: case INITIALIZED:
return GBApplication.getContext().getString(R.string.initialized); return GBApplication.getContext().getString(R.string.connected);
} }
return GBApplication.getContext().getString(R.string.unknown_state); return GBApplication.getContext().getString(R.string.unknown_state);
} }
public String getInfoString() {
if (mFirmwareVersion != null) {
if (mHardwareVersion != null) {
return GBApplication.getContext().getString(R.string.connectionstate_hw_fw, mHardwareVersion, mFirmwareVersion);
}
return GBApplication.getContext().getString(R.string.connectionstate_fw, mFirmwareVersion);
} else {
return "";
}
}
public DeviceType getType() { public DeviceType getType() {
return mDeviceType; return mDeviceType;
} }
@ -326,6 +331,49 @@ public class GBDevice implements Parcelable {
return ""; return "";
} }
public boolean hasDeviceInfos() {
return getDeviceInfos().size() > 0;
}
public List<ItemWithDetails> getDeviceInfos() {
List<ItemWithDetails> result = new ArrayList<>();
if (mDeviceInfos != null) {
result.addAll(mDeviceInfos);
}
if (mHardwareVersion != null) {
result.add(new GenericItem(DEVINFO_HW_VER, mHardwareVersion));
}
if (mFirmwareVersion != null) {
result.add(new GenericItem(DEVINFO_FW_VER, mFirmwareVersion));
}
Collections.sort(result);
return result;
}
public void setDeviceInfos(List<ItemWithDetails> deviceInfos) {
this.mDeviceInfos = deviceInfos;
}
public void addDeviceInfo(ItemWithDetails info) {
if (mDeviceInfos == null) {
mDeviceInfos = new ArrayList<>();
} else {
int index = mDeviceInfos.indexOf(info);
if (index >= 0) {
mDeviceInfos.set(index, info); // replace item with new one
return;
}
}
mDeviceInfos.add(info);
}
public boolean removeDeviceInfo(ItemWithDetails info) {
if (mDeviceInfos == null) {
return false;
}
return mDeviceInfos.remove(info);
}
public enum State { public enum State {
// Note: the order is important! // Note: the order is important!
NOT_CONNECTED, NOT_CONNECTED,
@ -333,6 +381,8 @@ public class GBDevice implements Parcelable {
CONNECTING, CONNECTING,
CONNECTED, CONNECTED,
INITIALIZING, INITIALIZING,
AUTHENTICATION_REQUIRED, // some kind of pairing is required by the device
AUTHENTICATING, // some kind of pairing is requested by the device
/** /**
* Means that the device is connected AND all the necessary initialization steps * Means that the device is connected AND all the necessary initialization steps
* have been performed. At the very least, this means that basic information like * have been performed. At the very least, this means that basic information like

View File

@ -12,6 +12,7 @@ public class GBDeviceApp {
private final UUID uuid; private final UUID uuid;
private final Type type; private final Type type;
private final boolean inCache; private final boolean inCache;
private final boolean configurable;
public GBDeviceApp(UUID uuid, String name, String creator, String version, Type type) { public GBDeviceApp(UUID uuid, String name, String creator, String version, Type type) {
this.uuid = uuid; this.uuid = uuid;
@ -21,9 +22,10 @@ public class GBDeviceApp {
this.type = type; this.type = type;
//FIXME: do not assume //FIXME: do not assume
this.inCache = false; this.inCache = false;
this.configurable = false;
} }
public GBDeviceApp(JSONObject json) { public GBDeviceApp(JSONObject json, boolean configurable) {
UUID uuid = UUID.fromString("00000000-0000-0000-0000-000000000000"); UUID uuid = UUID.fromString("00000000-0000-0000-0000-000000000000");
String name = ""; String name = "";
String creator = ""; String creator = "";
@ -47,6 +49,7 @@ public class GBDeviceApp {
this.type = type; this.type = type;
//FIXME: do not assume //FIXME: do not assume
this.inCache = true; this.inCache = true;
this.configurable = configurable;
} }
public boolean isInCache() { public boolean isInCache() {
@ -94,4 +97,8 @@ public class GBDeviceApp {
} }
return json; return json;
} }
public boolean isConfigurable() {
return configurable;
}
} }

View File

@ -10,9 +10,10 @@ import java.util.ArrayList;
import java.util.UUID; import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
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.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
public class GBDeviceService implements DeviceService { public class GBDeviceService implements DeviceService {
@ -115,19 +116,23 @@ public class GBDeviceService implements DeviceService {
} }
@Override @Override
public void onSetCallState(String number, String name, ServiceCommand command) { public void onSetCallState(CallSpec callSpec) {
// name is actually ignored and provided by the service itself... // name is actually ignored and provided by the service itself...
Intent intent = createIntent().setAction(ACTION_CALLSTATE) Intent intent = createIntent().setAction(ACTION_CALLSTATE)
.putExtra(EXTRA_CALL_PHONENUMBER, number) .putExtra(EXTRA_CALL_PHONENUMBER, callSpec.number)
.putExtra(EXTRA_CALL_COMMAND, command); .putExtra(EXTRA_CALL_COMMAND, callSpec.command);
invokeService(intent); invokeService(intent);
} }
@Override @Override
public void onSetMusicInfo(String artist, String album, String track) { public void onSetMusicInfo(MusicSpec musicSpec) {
Intent intent = createIntent().setAction(ACTION_SETMUSICINFO) Intent intent = createIntent().setAction(ACTION_SETMUSICINFO)
.putExtra(EXTRA_MUSIC_ARTIST, artist) .putExtra(EXTRA_MUSIC_ARTIST, musicSpec.artist)
.putExtra(EXTRA_MUSIC_TRACK, track); .putExtra(EXTRA_MUSIC_ALBUM, musicSpec.album)
.putExtra(EXTRA_MUSIC_TRACK, musicSpec.track)
.putExtra(EXTRA_MUSIC_DURATION, musicSpec.duration)
.putExtra(EXTRA_MUSIC_TRACKCOUNT, musicSpec.trackCount)
.putExtra(EXTRA_MUSIC_TRACKNR, musicSpec.trackNr);
invokeService(intent); invokeService(intent);
} }
@ -159,6 +164,14 @@ public class GBDeviceService implements DeviceService {
invokeService(intent); invokeService(intent);
} }
@Override
public void onAppConfiguration(UUID uuid, String config) {
Intent intent = createIntent().setAction(ACTION_APP_CONFIGURE)
.putExtra(EXTRA_APP_UUID, uuid)
.putExtra(EXTRA_APP_CONFIG, config);
invokeService(intent);
}
@Override @Override
public void onFetchActivityData() { public void onFetchActivityData() {
Intent intent = createIntent().setAction(ACTION_FETCH_ACTIVITY_DATA); Intent intent = createIntent().setAction(ACTION_FETCH_ACTIVITY_DATA);
@ -171,6 +184,12 @@ public class GBDeviceService implements DeviceService {
invokeService(intent); invokeService(intent);
} }
@Override
public void onHeartRateTest() {
Intent intent = createIntent().setAction(ACTION_HEARTRATE_TEST);
invokeService(intent);
}
@Override @Override
public void onFindDevice(boolean start) { public void onFindDevice(boolean start) {
Intent intent = createIntent().setAction(ACTION_FIND_DEVICE) Intent intent = createIntent().setAction(ACTION_FIND_DEVICE)
@ -187,7 +206,21 @@ public class GBDeviceService implements DeviceService {
@Override @Override
public void onEnableRealtimeSteps(boolean enable) { public void onEnableRealtimeSteps(boolean enable) {
Intent intent = createIntent().setAction(ACTION_ENABLE_REALTIME_STEPS) Intent intent = createIntent().setAction(ACTION_ENABLE_REALTIME_STEPS)
.putExtra(EXTRA_ENABLE_REALTIME_STEPS, enable); .putExtra(EXTRA_BOOLEAN_ENABLE, enable);
invokeService(intent);
}
@Override
public void onEnableHeartRateSleepSupport(boolean enable) {
Intent intent = createIntent().setAction(ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT)
.putExtra(EXTRA_BOOLEAN_ENABLE, enable);
invokeService(intent);
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
Intent intent = createIntent().setAction(ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT)
.putExtra(EXTRA_BOOLEAN_ENABLE, enable);
invokeService(intent); invokeService(intent);
} }
} }

View File

@ -14,8 +14,8 @@ public class ActivityKind {
public static final int TYPE_SLEEP = TYPE_LIGHT_SLEEP | TYPE_DEEP_SLEEP; public static final int TYPE_SLEEP = TYPE_LIGHT_SLEEP | TYPE_DEEP_SLEEP;
public static final int TYPE_ALL = TYPE_ACTIVITY | TYPE_SLEEP | TYPE_NOT_WORN; public static final int TYPE_ALL = TYPE_ACTIVITY | TYPE_SLEEP | TYPE_NOT_WORN;
public static byte[] mapToDBActivityTypes(int types, SampleProvider provider) { public static int[] mapToDBActivityTypes(int types, SampleProvider provider) {
byte[] result = new byte[3]; int[] result = new int[3];
int i = 0; int i = 0;
if ((types & ActivityKind.TYPE_ACTIVITY) != 0) { if ((types & ActivityKind.TYPE_ACTIVITY) != 0) {
result[i++] = provider.toRawActivityKind(TYPE_ACTIVITY); result[i++] = provider.toRawActivityKind(TYPE_ACTIVITY);

View File

@ -18,7 +18,7 @@ public interface ActivitySample {
/** /**
* Returns the raw activity kind value as recorded by the SampleProvider * Returns the raw activity kind value as recorded by the SampleProvider
*/ */
byte getRawKind(); int getRawKind();
/** /**
* Returns the activity kind value as recorded by the SampleProvider * Returns the activity kind value as recorded by the SampleProvider
@ -30,7 +30,7 @@ public interface ActivitySample {
/** /**
* Returns the raw intensity value as recorded by the SampleProvider * Returns the raw intensity value as recorded by the SampleProvider
*/ */
short getRawIntensity(); int getRawIntensity();
/** /**
* Returns the normalized intensity value between 0 and 1 * Returns the normalized intensity value between 0 and 1
@ -40,5 +40,7 @@ public interface ActivitySample {
/** /**
* Returns the number of steps performed during the period of this sample * Returns the number of steps performed during the period of this sample
*/ */
short getSteps(); int getSteps();
int getCustomValue();
} }

View File

@ -0,0 +1,94 @@
package nodomain.freeyourgadget.gadgetbridge.model;
import java.util.Calendar;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
/**
* Class holding the common user information needed by most activity trackers
*/
public class ActivityUser {
private Integer activityUserGender;
private Integer activityUserYearOfBirth;
private Integer activityUserHeightCm;
private Integer activityUserWeightKg;
private Integer activityUserSleepDuration;
public static final int defaultUserGender = 0;
public static final int defaultUserYearOfBirth = 0;
public static final int defaultUserAge = 0;
public static final int defaultUserHeightCm = 175;
public static final int defaultUserWeightKg = 70;
public static final int defaultUserSleepDuration = 7;
public static final String PREF_USER_YEAR_OF_BIRTH = "activity_user_year_of_birth";
public static final String PREF_USER_GENDER = "activity_user_gender";
public static final String PREF_USER_HEIGHT_CM = "activity_user_height_cm";
public static final String PREF_USER_WEIGHT_KG = "activity_user_weight_kg";
public static final String PREF_USER_SLEEP_DURATION = "activity_user_sleep_duration";
public int getActivityUserWeightKg() {
if (activityUserWeightKg == null) {
fetchPreferences();
}
return activityUserWeightKg;
}
public int getActivityUserGender() {
if (activityUserGender == null) {
fetchPreferences();
}
return activityUserGender;
}
public int getActivityUserYearOfBirth() {
if (activityUserYearOfBirth == null) {
fetchPreferences();
}
return activityUserYearOfBirth;
}
public int getActivityUserHeightCm() {
if (activityUserHeightCm == null) {
fetchPreferences();
}
return activityUserHeightCm;
}
/**
* @return the user defined sleep duration or the default value when none is set or the stored
* value is out of any logical bounds.
*/
public int getActivityUserSleepDuration() {
if (activityUserSleepDuration == null) {
fetchPreferences();
}
if (activityUserSleepDuration < 1 || activityUserSleepDuration > 24) {
activityUserSleepDuration = defaultUserSleepDuration;
}
return activityUserSleepDuration;
}
public int getActivityUserAge() {
int userYear = getActivityUserYearOfBirth();
int age = 25;
if (userYear > 1900) {
age = Calendar.getInstance().get(Calendar.YEAR) - userYear;
if (age <= 0) {
age = 25;
}
}
return age;
}
private void fetchPreferences() {
Prefs prefs = GBApplication.getPrefs();
activityUserGender = prefs.getInt(PREF_USER_GENDER, defaultUserGender);
activityUserHeightCm = prefs.getInt(PREF_USER_HEIGHT_CM, defaultUserHeightCm);
activityUserWeightKg = prefs.getInt(PREF_USER_WEIGHT_KG, defaultUserWeightKg);
activityUserYearOfBirth = prefs.getInt(PREF_USER_YEAR_OF_BIRTH, defaultUserYearOfBirth);
activityUserSleepDuration = prefs.getInt(PREF_USER_SLEEP_DURATION, defaultUserSleepDuration);
}
}

View File

@ -22,7 +22,7 @@ public class CalendarEvents {
// needed for miband: // needed for miband:
// time // time
private static final String[] EVENT_INSTANCE_PROJECTION = new String[] { private static final String[] EVENT_INSTANCE_PROJECTION = new String[]{
Instances._ID, Instances._ID,
Instances.BEGIN, Instances.BEGIN,
Instances.END, Instances.END,
@ -54,11 +54,11 @@ public class CalendarEvents {
ContentUris.appendId(eventsUriBuilder, dtEnd); ContentUris.appendId(eventsUriBuilder, dtEnd);
Uri eventsUri = eventsUriBuilder.build(); Uri eventsUri = eventsUriBuilder.build();
Cursor evtCursor = null; try (Cursor evtCursor = mContext.getContentResolver().query(eventsUri, EVENT_INSTANCE_PROJECTION, null, null, CalendarContract.Instances.BEGIN + " ASC")) {
evtCursor = mContext.getContentResolver().query(eventsUri, EVENT_INSTANCE_PROJECTION, null, null, CalendarContract.Instances.BEGIN + " ASC"); if (evtCursor == null || evtCursor.getCount() == 0) {
return false;
if (evtCursor.moveToFirst()) { }
do { while (evtCursor.moveToNext()) {
CalendarEvent calEvent = new CalendarEvent( CalendarEvent calEvent = new CalendarEvent(
evtCursor.getLong(1), evtCursor.getLong(1),
evtCursor.getLong(2), evtCursor.getLong(2),
@ -67,13 +67,11 @@ public class CalendarEvents {
evtCursor.getString(5), evtCursor.getString(5),
evtCursor.getString(6), evtCursor.getString(6),
evtCursor.getString(7) evtCursor.getString(7)
); );
calendarEventList.add(calEvent); calendarEventList.add(calEvent);
} while(evtCursor.moveToNext()); }
return true; return true;
} }
return false;
} }
public class CalendarEvent { public class CalendarEvent {
@ -100,7 +98,7 @@ public class CalendarEvents {
} }
public int getBeginSeconds() { public int getBeginSeconds() {
return (int)(begin/1000); return (int) (begin / 1000);
} }
public long getEnd() { public long getEnd() {
@ -112,11 +110,11 @@ public class CalendarEvents {
} }
public int getDurationSeconds() { public int getDurationSeconds() {
return (int)((getDuration())/1000); return (int) ((getDuration()) / 1000);
} }
public short getDurationMinutes() { public short getDurationMinutes() {
return (short)(getDurationSeconds()/60); return (short) (getDurationSeconds() / 60);
} }

View File

@ -0,0 +1,15 @@
package nodomain.freeyourgadget.gadgetbridge.model;
public class CallSpec {
public static final int CALL_UNDEFINED = 1;
public static final int CALL_ACCEPT = 1;
public static final int CALL_INCOMING = 2;
public static final int CALL_OUTGOING = 3;
public static final int CALL_REJECT = 4;
public static final int CALL_START = 5;
public static final int CALL_END = 6;
public String number;
public String name;
public int command;
}

View File

@ -15,7 +15,6 @@ public interface DeviceService extends EventHandler {
String ACTION_START = PREFIX + ".action.start"; String ACTION_START = PREFIX + ".action.start";
String ACTION_CONNECT = PREFIX + ".action.connect"; String ACTION_CONNECT = PREFIX + ".action.connect";
String ACTION_NOTIFICATION = PREFIX + ".action.notification"; String ACTION_NOTIFICATION = PREFIX + ".action.notification";
String ACTION_NOTIFICATION_SMS = PREFIX + ".action.notification_sms";
String ACTION_CALLSTATE = PREFIX + ".action.callstate"; String ACTION_CALLSTATE = PREFIX + ".action.callstate";
String ACTION_SETTIME = PREFIX + ".action.settime"; String ACTION_SETTIME = PREFIX + ".action.settime";
String ACTION_SETMUSICINFO = PREFIX + ".action.setmusicinfo"; String ACTION_SETMUSICINFO = PREFIX + ".action.setmusicinfo";
@ -24,15 +23,19 @@ public interface DeviceService extends EventHandler {
String ACTION_REQUEST_SCREENSHOT = PREFIX + ".action.request_screenshot"; String ACTION_REQUEST_SCREENSHOT = PREFIX + ".action.request_screenshot";
String ACTION_STARTAPP = PREFIX + ".action.startapp"; String ACTION_STARTAPP = PREFIX + ".action.startapp";
String ACTION_DELETEAPP = PREFIX + ".action.deleteapp"; String ACTION_DELETEAPP = PREFIX + ".action.deleteapp";
String ACTION_APP_CONFIGURE = PREFIX + ".action.app_configure";
String ACTION_INSTALL = PREFIX + ".action.install"; String ACTION_INSTALL = PREFIX + ".action.install";
String ACTION_REBOOT = PREFIX + ".action.reboot"; String ACTION_REBOOT = PREFIX + ".action.reboot";
String ACTION_HEARTRATE_TEST = PREFIX + ".action.heartrate_test";
String ACTION_FETCH_ACTIVITY_DATA = PREFIX + ".action.fetch_activity_data"; String ACTION_FETCH_ACTIVITY_DATA = PREFIX + ".action.fetch_activity_data";
String ACTION_DISCONNECT = PREFIX + ".action.disconnect"; String ACTION_DISCONNECT = PREFIX + ".action.disconnect";
String ACTION_FIND_DEVICE = PREFIX + ".action.find_device"; String ACTION_FIND_DEVICE = PREFIX + ".action.find_device";
String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms"; String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms";
String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps"; String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
String ACTION_REALTIME_STEPS = PREFIX + ".action.realtime_steps"; String ACTION_REALTIME_STEPS = PREFIX + ".action.realtime_steps";
String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement";
String ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT = PREFIX + ".action.enable_heartrate_sleep_support";
String ACTION_HEARTRATE_MEASUREMENT = PREFIX + ".action.hr_measurement";
String EXTRA_DEVICE_ADDRESS = "device_address"; String EXTRA_DEVICE_ADDRESS = "device_address";
String EXTRA_NOTIFICATION_BODY = "notification_body"; String EXTRA_NOTIFICATION_BODY = "notification_body";
String EXTRA_NOTIFICATION_FLAGS = "notification_flags"; String EXTRA_NOTIFICATION_FLAGS = "notification_flags";
@ -49,14 +52,19 @@ public interface DeviceService extends EventHandler {
String EXTRA_MUSIC_ARTIST = "music_artist"; String EXTRA_MUSIC_ARTIST = "music_artist";
String EXTRA_MUSIC_ALBUM = "music_album"; String EXTRA_MUSIC_ALBUM = "music_album";
String EXTRA_MUSIC_TRACK = "music_track"; String EXTRA_MUSIC_TRACK = "music_track";
String EXTRA_MUSIC_DURATION = "music_duration";
String EXTRA_MUSIC_TRACKNR = "music_tracknr";
String EXTRA_MUSIC_TRACKCOUNT = "music_trackcount";
String EXTRA_APP_UUID = "app_uuid"; String EXTRA_APP_UUID = "app_uuid";
String EXTRA_APP_START = "app_start"; String EXTRA_APP_START = "app_start";
String EXTRA_APP_CONFIG = "app_config";
String EXTRA_URI = "uri"; String EXTRA_URI = "uri";
String EXTRA_ALARMS = "alarms"; String EXTRA_ALARMS = "alarms";
String EXTRA_PERFORM_PAIR = "perform_pair"; String EXTRA_PERFORM_PAIR = "perform_pair";
String EXTRA_ENABLE_REALTIME_STEPS = "enable_realtime_steps"; String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
String EXTRA_REALTIME_STEPS = "realtime_steps"; String EXTRA_REALTIME_STEPS = "realtime_steps";
String EXTRA_TIMESTAMP = "timestamp"; String EXTRA_TIMESTAMP = "timestamp";
String EXTRA_HEART_RATE_VALUE = "hr_value";
void start(); void start();

View File

@ -1,10 +1,31 @@
package nodomain.freeyourgadget.gadgetbridge.model; package nodomain.freeyourgadget.gadgetbridge.model;
import android.os.Parcel;
import android.os.Parcelable;
import java.text.Collator;
public class GenericItem implements ItemWithDetails { public class GenericItem implements ItemWithDetails {
private String name; private String name;
private String details; private String details;
private int icon; private int icon;
public static final Parcelable.Creator CREATOR = new Parcelable.Creator<GenericItem>() {
@Override
public GenericItem createFromParcel(Parcel source) {
GenericItem item = new GenericItem();
item.setName(source.readString());
item.setDetails(source.readString());
item.setIcon(source.readInt());
return item;
}
@Override
public GenericItem[] newArray(int size) {
return new GenericItem[size];
}
};
public GenericItem(String name, String details) { public GenericItem(String name, String details) {
this.name = name; this.name = name;
this.details = details; this.details = details;
@ -17,6 +38,13 @@ public class GenericItem implements ItemWithDetails {
public GenericItem() { public GenericItem() {
} }
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(getName());
dest.writeString(getDetails());
dest.writeInt(getIcon());
}
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
@ -43,4 +71,38 @@ public class GenericItem implements ItemWithDetails {
public int getIcon() { public int getIcon() {
return icon; return icon;
} }
@Override
public int describeContents() {
return 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GenericItem that = (GenericItem) o;
return !(getName() != null ? !getName().equals(that.getName()) : that.getName() != null);
}
@Override
public int hashCode() {
return getName() != null ? getName().hashCode() : 0;
}
@Override
public int compareTo(ItemWithDetails another) {
if (getName() == another.getName()) {
return 0;
}
if (getName() == null) {
return +1;
} else if (another.getName() == null) {
return -1;
}
return Collator.getInstance().compare(getName(), another.getName());
}
} }

View File

@ -1,9 +1,18 @@
package nodomain.freeyourgadget.gadgetbridge.model; package nodomain.freeyourgadget.gadgetbridge.model;
public interface ItemWithDetails { import android.os.Parcelable;
public interface ItemWithDetails extends Parcelable, Comparable<ItemWithDetails> {
String getName(); String getName();
String getDetails(); String getDetails();
int getIcon(); int getIcon();
/**
* Equality is based on #getName() only.
*
* @param other
*/
boolean equals(Object other);
} }

View File

@ -0,0 +1,33 @@
package nodomain.freeyourgadget.gadgetbridge.model;
public class Measurement {
private final int value;
private final long timestamp;
public Measurement(int value, long timestamp) {
this.value = value;
this.timestamp = timestamp;
}
public int getValue() {
return value;
}
public long getTimestamp() {
return timestamp;
}
@Override
public int hashCode() {
return (int) (71 ^ value ^ timestamp);
}
@Override
public boolean equals(Object o) {
if (o instanceof Measurement) {
Measurement m = (Measurement) o;
return timestamp == m.timestamp && value == m.value;
}
return super.equals(o);
}
}

View File

@ -0,0 +1,17 @@
package nodomain.freeyourgadget.gadgetbridge.model;
public class MusicSpec {
public static final int MUSIC_UNDEFINED = 0;
public static final int MUSIC_PLAY = 1;
public static final int MUSIC_PAUSE = 2;
public static final int MUSIC_PLAYPAUSE = 3;
public static final int MUSIC_NEXT = 4;
public static final int MUSIC_PREVIOUS = 5;
public String artist;
public String album;
public String track;
public int duration;
public int trackCount;
public int trackNr;
}

View File

@ -1,22 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge.model;
public enum ServiceCommand {
UNDEFINED,
CALL_ACCEPT,
CALL_END,
CALL_INCOMING,
CALL_OUTGOING,
CALL_REJECT,
CALL_START,
MUSIC_PLAY,
MUSIC_PAUSE,
MUSIC_PLAYPAUSE,
MUSIC_NEXT,
MUSIC_PREVIOUS,
APP_INFO_NAME,
VERSION_FIRMWARE
}

View File

@ -6,11 +6,9 @@ import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.telephony.SmsManager; import android.telephony.SmsManager;
@ -32,6 +30,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
@ -43,6 +42,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBCallControlReceiver; import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBCallControlReceiver;
import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBMusicControlReceiver; import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBMusicControlReceiver;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
// TODO: support option for a single reminder notification when notifications could not be delivered? // TODO: support option for a single reminder notification when notifications could not be delivered?
// conditions: app was running and received notifications, but device was not connected. // conditions: app was running and received notifications, but device was not connected.
@ -59,6 +59,7 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
protected GBDevice gbDevice; protected GBDevice gbDevice;
private BluetoothAdapter btAdapter; private BluetoothAdapter btAdapter;
private Context context; private Context context;
private boolean autoReconnect;
public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) { public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
this.gbDevice = gbDevice; this.gbDevice = gbDevice;
@ -81,6 +82,16 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
return gbDevice.isInitialized(); return gbDevice.isInitialized();
} }
@Override
public void setAutoReconnect(boolean enable) {
autoReconnect = enable;
}
@Override
public boolean getAutoReconnect() {
return autoReconnect;
}
@Override @Override
public GBDevice getDevice() { public GBDevice getDevice() {
return gbDevice; return gbDevice;
@ -246,8 +257,8 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
Intent notificationListenerIntent = new Intent(action); Intent notificationListenerIntent = new Intent(action);
notificationListenerIntent.putExtra("handle", deviceEvent.handle); notificationListenerIntent.putExtra("handle", deviceEvent.handle);
if (deviceEvent.reply != null) { if (deviceEvent.reply != null) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext()); Prefs prefs = GBApplication.getPrefs();
String suffix = sharedPrefs.getString("canned_reply_suffix", null); String suffix = prefs.getString("canned_reply_suffix", null);
if (suffix != null && !Objects.equals(suffix, "")) { if (suffix != null && !Objects.equals(suffix, "")) {
deviceEvent.reply += suffix; deviceEvent.reply += suffix;
} }
@ -280,4 +291,14 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
gbDevice.sendDeviceUpdateIntent(context); gbDevice.sendDeviceUpdateIntent(context);
} }
public void handleGBDeviceEvent(GBDeviceEventDisplayMessage message) {
GB.log(message.message, message.severity, null);
Intent messageIntent = new Intent(GB.ACTION_DISPLAY_MESSAGE);
messageIntent.putExtra(GB.DISPLAY_MESSAGE_MESSAGE, message.message);
messageIntent.putExtra(GB.DISPLAY_MESSAGE_DURATION, message.duration);
messageIntent.putExtra(GB.DISPLAY_MESSAGE_SEVERITY, message.severity);
LocalBroadcastManager.getInstance(context).sendBroadcast(messageIntent);
}
} }

View File

@ -3,7 +3,6 @@ package nodomain.freeyourgadget.gadgetbridge.service;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.Service; import android.app.Service;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
@ -11,7 +10,6 @@ import android.content.SharedPreferences;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.IBinder; import android.os.IBinder;
import android.preference.PreferenceManager;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
@ -34,19 +32,26 @@ import nodomain.freeyourgadget.gadgetbridge.externalevents.SMSReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
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.ServiceCommand;
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 nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_CONFIGURE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CALLSTATE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CALLSTATE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DISCONNECT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DISCONNECT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_STEPS; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FETCH_ACTIVITY_DATA; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FETCH_ACTIVITY_DATA;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FIND_DEVICE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FIND_DEVICE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_HEARTRATE_TEST;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_INSTALL; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_INSTALL;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REBOOT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REBOOT;
@ -59,16 +64,20 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SE
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ALARMS; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ALARMS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_CONFIG;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_START; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_UUID; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_UUID;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_BOOLEAN_ENABLE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_COMMAND; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_COMMAND;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_PHONENUMBER; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_PHONENUMBER;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_DEVICE_ADDRESS; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_DEVICE_ADDRESS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ENABLE_REALTIME_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_DURATION;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACK; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACK;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACKCOUNT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACKNR;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_BODY; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_BODY;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_FLAGS; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_FLAGS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_ID; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_ID;
@ -81,7 +90,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOT
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_PERFORM_PAIR; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_PERFORM_PAIR;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI;
public class DeviceCommunicationService extends Service { public class DeviceCommunicationService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class); private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class);
private boolean mStarted = false; private boolean mStarted = false;
@ -109,7 +118,7 @@ public class DeviceCommunicationService extends Service {
mGBDevice = device; mGBDevice = device;
boolean enableReceivers = mDeviceSupport != null && (mDeviceSupport.useAutoConnect() || mGBDevice.isInitialized()); boolean enableReceivers = mDeviceSupport != null && (mDeviceSupport.useAutoConnect() || mGBDevice.isInitialized());
setReceiversEnableState(enableReceivers); setReceiversEnableState(enableReceivers);
GB.updateNotification(mGBDevice.getName() + " " + mGBDevice.getStateString(), context); GB.updateNotification(mGBDevice.getName() + " " + mGBDevice.getStateString(), mGBDevice.isInitialized(), context);
} else { } else {
LOG.error("Got ACTION_DEVICE_CHANGED from unexpected device: " + mGBDevice); LOG.error("Got ACTION_DEVICE_CHANGED from unexpected device: " + mGBDevice);
} }
@ -123,6 +132,10 @@ public class DeviceCommunicationService extends Service {
super.onCreate(); super.onCreate();
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED)); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
mFactory = new DeviceSupportFactory(this); mFactory = new DeviceSupportFactory(this);
if (hasPrefs()) {
getPrefs().getPreferences().registerOnSharedPreferenceChangeListener(this);
}
} }
@Override @Override
@ -162,6 +175,7 @@ public class DeviceCommunicationService extends Service {
// when we get past this, we should have valid mDeviceSupport and mGBDevice instances // when we get past this, we should have valid mDeviceSupport and mGBDevice instances
Prefs prefs = getPrefs();
switch (action) { switch (action) {
case ACTION_START: case ACTION_START:
start(); start();
@ -169,19 +183,23 @@ public class DeviceCommunicationService extends Service {
case ACTION_CONNECT: case ACTION_CONNECT:
start(); // ensure started start(); // ensure started
GBDevice gbDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE); GBDevice gbDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
String btDeviceAddress = null;
if (gbDevice == null) { if (gbDevice == null) {
String btDeviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS); btDeviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS);
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); if (btDeviceAddress == null && prefs != null) { // may be null in test cases
if (sharedPrefs != null) { // may be null in test cases btDeviceAddress = prefs.getString("last_device_address", null);
if (btDeviceAddress == null) {
btDeviceAddress = sharedPrefs.getString("last_device_address", null);
} else {
sharedPrefs.edit().putString("last_device_address", btDeviceAddress).apply();
}
} }
if (btDeviceAddress != null) { if (btDeviceAddress != null) {
gbDevice = DeviceHelper.getInstance().findAvailableDevice(btDeviceAddress, this); gbDevice = DeviceHelper.getInstance().findAvailableDevice(btDeviceAddress, this);
} }
} else {
btDeviceAddress = gbDevice.getAddress();
}
boolean autoReconnect = GBPrefs.AUTO_RECONNECT_DEFAULT;
if (prefs != null && prefs.getPreferences() != null) {
prefs.getPreferences().edit().putString("last_device_address", btDeviceAddress).apply();
autoReconnect = getGBPrefs().getAutoReconnect();
} }
if (gbDevice != null && !isConnecting() && !isConnected()) { if (gbDevice != null && !isConnecting() && !isConnected()) {
@ -193,6 +211,7 @@ public class DeviceCommunicationService extends Service {
if (pair) { if (pair) {
deviceSupport.pair(); deviceSupport.pair();
} else { } else {
deviceSupport.setAutoReconnect(autoReconnect);
deviceSupport.connect(); deviceSupport.connect();
} }
} else { } else {
@ -230,13 +249,12 @@ public class DeviceCommunicationService extends Service {
if (((notificationSpec.flags & NotificationSpec.FLAG_WEARABLE_REPLY) > 0) if (((notificationSpec.flags & NotificationSpec.FLAG_WEARABLE_REPLY) > 0)
|| (notificationSpec.type == NotificationType.SMS && notificationSpec.phoneNumber != null)) { || (notificationSpec.type == NotificationType.SMS && notificationSpec.phoneNumber != null)) {
// NOTE: maybe not where it belongs // NOTE: maybe not where it belongs
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); if (prefs.getBoolean("pebble_force_untested", false)) {
if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
// I would rather like to save that as an array in ShadredPreferences // I would rather like to save that as an array in ShadredPreferences
// this would work but I dont know how to do the same in the Settings Activity's xml // this would work but I dont know how to do the same in the Settings Activity's xml
ArrayList<String> replies = new ArrayList<>(); ArrayList<String> replies = new ArrayList<>();
for (int i = 1; i <= 16; i++) { for (int i = 1; i <= 16; i++) {
String reply = sharedPrefs.getString("canned_reply_" + i, null); String reply = prefs.getString("canned_reply_" + i, null);
if (reply != null && !reply.equals("")) { if (reply != null && !reply.equals("")) {
replies.add(reply); replies.add(reply);
} }
@ -251,6 +269,10 @@ public class DeviceCommunicationService extends Service {
mDeviceSupport.onReboot(); mDeviceSupport.onReboot();
break; break;
} }
case ACTION_HEARTRATE_TEST: {
mDeviceSupport.onHeartRateTest();
break;
}
case ACTION_FETCH_ACTIVITY_DATA: { case ACTION_FETCH_ACTIVITY_DATA: {
mDeviceSupport.onFetchActivityData(); mDeviceSupport.onFetchActivityData();
break; break;
@ -266,23 +288,32 @@ public class DeviceCommunicationService extends Service {
break; break;
} }
case ACTION_CALLSTATE: case ACTION_CALLSTATE:
ServiceCommand command = (ServiceCommand) intent.getSerializableExtra(EXTRA_CALL_COMMAND); int command = intent.getIntExtra(EXTRA_CALL_COMMAND, CallSpec.CALL_UNDEFINED);
String phoneNumber = intent.getStringExtra(EXTRA_CALL_PHONENUMBER); String phoneNumber = intent.getStringExtra(EXTRA_CALL_PHONENUMBER);
String callerName = null; String callerName = null;
if (phoneNumber != null) { if (phoneNumber != null) {
callerName = getContactDisplayNameByNumber(phoneNumber); callerName = getContactDisplayNameByNumber(phoneNumber);
} }
mDeviceSupport.onSetCallState(phoneNumber, callerName, command);
CallSpec callSpec = new CallSpec();
callSpec.command = command;
callSpec.number = phoneNumber;
callSpec.name = callerName;
mDeviceSupport.onSetCallState(callSpec);
break; break;
case ACTION_SETTIME: case ACTION_SETTIME:
mDeviceSupport.onSetTime(); mDeviceSupport.onSetTime();
break; break;
case ACTION_SETMUSICINFO: case ACTION_SETMUSICINFO:
String artist = intent.getStringExtra(EXTRA_MUSIC_ARTIST); MusicSpec musicSpec = new MusicSpec();
String album = intent.getStringExtra(EXTRA_MUSIC_ALBUM); musicSpec.artist = intent.getStringExtra(EXTRA_MUSIC_ARTIST);
String track = intent.getStringExtra(EXTRA_MUSIC_TRACK); musicSpec.album = intent.getStringExtra(EXTRA_MUSIC_ALBUM);
mDeviceSupport.onSetMusicInfo(artist, album, track); musicSpec.track = intent.getStringExtra(EXTRA_MUSIC_TRACK);
musicSpec.duration = intent.getIntExtra(EXTRA_MUSIC_DURATION, 0);
musicSpec.trackCount = intent.getIntExtra(EXTRA_MUSIC_TRACKCOUNT, 0);
musicSpec.trackNr = intent.getIntExtra(EXTRA_MUSIC_TRACKNR, 0);
mDeviceSupport.onSetMusicInfo(musicSpec);
break; break;
case ACTION_REQUEST_APPINFO: case ACTION_REQUEST_APPINFO:
mDeviceSupport.onAppInfoReq(); mDeviceSupport.onAppInfoReq();
@ -301,6 +332,11 @@ public class DeviceCommunicationService extends Service {
mDeviceSupport.onAppDelete(uuid); mDeviceSupport.onAppDelete(uuid);
break; break;
} }
case ACTION_APP_CONFIGURE: {
UUID uuid = (UUID) intent.getSerializableExtra(EXTRA_APP_UUID);
String config = intent.getStringExtra(EXTRA_APP_CONFIG);
mDeviceSupport.onAppConfiguration(uuid, config);
}
case ACTION_INSTALL: case ACTION_INSTALL:
Uri uri = intent.getParcelableExtra(EXTRA_URI); Uri uri = intent.getParcelableExtra(EXTRA_URI);
if (uri != null) { if (uri != null) {
@ -312,10 +348,21 @@ public class DeviceCommunicationService extends Service {
ArrayList<Alarm> alarms = intent.getParcelableArrayListExtra(EXTRA_ALARMS); ArrayList<Alarm> alarms = intent.getParcelableArrayListExtra(EXTRA_ALARMS);
mDeviceSupport.onSetAlarms(alarms); mDeviceSupport.onSetAlarms(alarms);
break; break;
case ACTION_ENABLE_REALTIME_STEPS: case ACTION_ENABLE_REALTIME_STEPS: {
boolean enable = intent.getBooleanExtra(EXTRA_ENABLE_REALTIME_STEPS, false); boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
mDeviceSupport.onEnableRealtimeSteps(enable); mDeviceSupport.onEnableRealtimeSteps(enable);
break; break;
}
case ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT: {
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
mDeviceSupport.onEnableHeartRateSleepSupport(enable);
break;
}
case ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT: {
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
mDeviceSupport.onEnableRealtimeHeartRateMeasurement(enable);
break;
}
} }
return START_STICKY; return START_STICKY;
@ -348,7 +395,7 @@ public class DeviceCommunicationService extends Service {
private void start() { private void start() {
if (!mStarted) { if (!mStarted) {
startForeground(GB.NOTIFICATION_ID, GB.createNotification(getString(R.string.gadgetbridge_running), this)); startForeground(GB.NOTIFICATION_ID, GB.createNotification(getString(R.string.gadgetbridge_running), false, this));
mStarted = true; mStarted = true;
} }
} }
@ -398,7 +445,10 @@ public class DeviceCommunicationService extends Service {
} }
if (mMusicPlaybackReceiver == null) { if (mMusicPlaybackReceiver == null) {
mMusicPlaybackReceiver = new MusicPlaybackReceiver(); mMusicPlaybackReceiver = new MusicPlaybackReceiver();
registerReceiver(mMusicPlaybackReceiver, new IntentFilter("com.android.music.metachanged")); IntentFilter filter = new IntentFilter();
filter.addAction("com.android.music.metachanged");
//filter.addAction("com.android.music.playstatechanged");
registerReceiver(mMusicPlaybackReceiver, filter);
} }
if (mTimeChangeReceiver == null) { if (mTimeChangeReceiver == null) {
mTimeChangeReceiver = new TimeChangeReceiver(); mTimeChangeReceiver = new TimeChangeReceiver();
@ -437,6 +487,10 @@ public class DeviceCommunicationService extends Service {
@Override @Override
public void onDestroy() { public void onDestroy() {
if (hasPrefs()) {
getPrefs().getPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
LOG.debug("DeviceCommunicationService is being destroyed"); LOG.debug("DeviceCommunicationService is being destroyed");
super.onDestroy(); super.onDestroy();
@ -462,26 +516,37 @@ public class DeviceCommunicationService extends Service {
return name; return name;
} }
ContentResolver contentResolver = getContentResolver(); try (Cursor contactLookup = getContentResolver().query(uri, null, null, null, null)) {
Cursor contactLookup = null;
try {
contactLookup = contentResolver.query(uri, null, null, null, null);
} catch (SecurityException e) {
return name;
}
try {
if (contactLookup != null && contactLookup.getCount() > 0) { if (contactLookup != null && contactLookup.getCount() > 0) {
contactLookup.moveToNext(); contactLookup.moveToNext();
name = contactLookup.getString(contactLookup.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)); name = contactLookup.getString(contactLookup.getColumnIndex(ContactsContract.Data.DISPLAY_NAME));
} }
} finally { } catch (SecurityException e) {
if (contactLookup != null) { // ignore, just return name below
contactLookup.close();
}
} }
return name; return name;
} }
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (GBPrefs.AUTO_RECONNECT.equals(key)) {
boolean autoReconnect = getGBPrefs().getAutoReconnect();
if (mDeviceSupport != null) {
mDeviceSupport.setAutoReconnect(autoReconnect);
}
}
}
protected boolean hasPrefs() {
return getPrefs().getPreferences() != null;
}
public Prefs getPrefs() {
return GBApplication.getPrefs();
}
public GBPrefs getGBPrefs() {
return GBApplication.getGBPrefs();
}
} }

View File

@ -62,6 +62,20 @@ public interface DeviceSupport extends EventHandler {
*/ */
boolean useAutoConnect(); boolean useAutoConnect();
/**
* Configures this instance to automatically attempt to reconnect after a connection loss.
* How, how long, or how often is up to the implementation.
* Note that tome implementations may not support automatic reconnection at all.
* @param enable
*/
void setAutoReconnect(boolean enable);
/**
* Returns whether this instance to configured to automatically attempt to reconnect after a
* connection loss.
*/
boolean getAutoReconnect();
/** /**
* Attempts to pair and connect this device with the gadget device. Success * Attempts to pair and connect this device with the gadget device. Success
* will be reported via a device change Intent. * will be reported via a device change Intent.

View File

@ -1,7 +1,6 @@
package nodomain.freeyourgadget.gadgetbridge.service; package nodomain.freeyourgadget.gadgetbridge.service;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context; import android.content.Context;
import android.widget.Toast; import android.widget.Toast;
@ -11,10 +10,8 @@ import java.util.EnumSet;
import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class DeviceSupportFactory { public class DeviceSupportFactory {

View File

@ -13,8 +13,9 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
/** /**
* Wraps another device support instance and supports busy-checking and throttling of events. * Wraps another device support instance and supports busy-checking and throttling of events.
@ -55,6 +56,16 @@ public class ServiceDeviceSupport implements DeviceSupport {
return delegate.connect(); return delegate.connect();
} }
@Override
public void setAutoReconnect(boolean enable) {
delegate.setAutoReconnect(enable);
}
@Override
public boolean getAutoReconnect() {
return delegate.getAutoReconnect();
}
@Override @Override
public void dispose() { public void dispose() {
delegate.dispose(); delegate.dispose();
@ -131,19 +142,19 @@ public class ServiceDeviceSupport implements DeviceSupport {
// No throttling for the other events // No throttling for the other events
@Override @Override
public void onSetCallState(String number, String name, ServiceCommand command) { public void onSetCallState(CallSpec callSpec) {
if (checkBusy("set call state")) { if (checkBusy("set call state")) {
return; return;
} }
delegate.onSetCallState(number, name, command); delegate.onSetCallState(callSpec);
} }
@Override @Override
public void onSetMusicInfo(String artist, String album, String track) { public void onSetMusicInfo(MusicSpec musicSpec) {
if (checkBusy("set music info")) { if (checkBusy("set music info")) {
return; return;
} }
delegate.onSetMusicInfo(artist, album, track); delegate.onSetMusicInfo(musicSpec);
} }
@Override @Override
@ -178,6 +189,14 @@ public class ServiceDeviceSupport implements DeviceSupport {
delegate.onAppDelete(uuid); delegate.onAppDelete(uuid);
} }
@Override
public void onAppConfiguration(UUID uuid, String config) {
if (checkBusy("app configuration")) {
return;
}
delegate.onAppConfiguration(uuid, config);
}
@Override @Override
public void onFetchActivityData() { public void onFetchActivityData() {
if (checkBusy("fetch activity data")) { if (checkBusy("fetch activity data")) {
@ -194,6 +213,14 @@ public class ServiceDeviceSupport implements DeviceSupport {
delegate.onReboot(); delegate.onReboot();
} }
@Override
public void onHeartRateTest() {
if (checkBusy("heartrate")) {
return;
}
delegate.onHeartRateTest();
}
@Override @Override
public void onFindDevice(boolean start) { public void onFindDevice(boolean start) {
if (checkBusy("find device")) { if (checkBusy("find device")) {
@ -225,4 +252,20 @@ public class ServiceDeviceSupport implements DeviceSupport {
} }
delegate.onEnableRealtimeSteps(enable); delegate.onEnableRealtimeSteps(enable);
} }
@Override
public void onEnableHeartRateSleepSupport(boolean enable) {
if (checkBusy("enable heartrate sleep support: " + enable)) {
return;
}
delegate.onEnableHeartRateSleepSupport(enable);
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
if (checkBusy("enable realtime heart rate measurement: " + enable)) {
return;
}
delegate.onEnableRealtimeHeartRateMeasurement(enable);
}
} }

View File

@ -16,6 +16,7 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction;
/** /**
* Abstract base class for all devices connected through Bluetooth Low Energy (LE) aka * Abstract base class for all devices connected through Bluetooth Low Energy (LE) aka
@ -41,10 +42,19 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
public boolean connect() { public boolean connect() {
if (mQueue == null) { if (mQueue == null) {
mQueue = new BtLEQueue(getBluetoothAdapter(), getDevice(), this, getContext()); mQueue = new BtLEQueue(getBluetoothAdapter(), getDevice(), this, getContext());
mQueue.setAutoReconnect(getAutoReconnect());
} }
return mQueue.connect(); return mQueue.connect();
} }
@Override
public void setAutoReconnect(boolean enable) {
super.setAutoReconnect(enable);
if (mQueue != null) {
mQueue.setAutoReconnect(enable);
}
}
/** /**
* Subclasses should populate the given builder to initialize the device (if necessary). * Subclasses should populate the given builder to initialize the device (if necessary).
* *

View File

@ -36,6 +36,7 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
* Performs this operation. The whole operation is asynchronous, i.e. * Performs this operation. The whole operation is asynchronous, i.e.
* this method quickly returns before the actual operation is finished. * this method quickly returns before the actual operation is finished.
* Calls #prePerform() and, if successful, #doPerform(). * Calls #prePerform() and, if successful, #doPerform().
*
* @throws IOException * @throws IOException
*/ */
@Override @Override
@ -48,6 +49,7 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
/** /**
* Hook for subclasses to perform something before #doPerform() is invoked. * Hook for subclasses to perform something before #doPerform() is invoked.
*
* @throws IOException * @throws IOException
*/ */
protected void prePerform() throws IOException { protected void prePerform() throws IOException {
@ -58,6 +60,7 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
* successfully. * successfully.
* Note that subclasses HAVE TO call #operationFinished() when the entire * Note that subclasses HAVE TO call #operationFinished() when the entire
* opreation is done (successful or not). * opreation is done (successful or not).
*
* @throws IOException * @throws IOException
*/ */
protected abstract void doPerform() throws IOException; protected abstract void doPerform() throws IOException;
@ -65,6 +68,7 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
/** /**
* You MUST call this method when the operation has finished, either * You MUST call this method when the operation has finished, either
* successfull or unsuccessfully. * successfull or unsuccessfully.
*
* @throws IOException * @throws IOException
*/ */
protected void operationFinished() throws IOException { protected void operationFinished() throws IOException {
@ -109,6 +113,10 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
return operationStatus == OperationStatus.RUNNING; return operationStatus == OperationStatus.RUNNING;
} }
public boolean isOperationFinished() {
return operationStatus == OperationStatus.FINISHED;
}
public T getSupport() { public T getSupport() {
return mSupport; return mSupport;
} }

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