mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-29 05:16:51 +01:00
Merge branch 'master' into feature-weather
This commit is contained in:
commit
dafdb1008d
2
.gitignore
vendored
2
.gitignore
vendored
@ -29,3 +29,5 @@ proguard/
|
||||
*.iml
|
||||
|
||||
MPChartLib
|
||||
|
||||
fw.dirs
|
||||
|
10
.travis.yml
10
.travis.yml
@ -1,4 +1,12 @@
|
||||
language: android
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- oraclejdk7
|
||||
|
||||
env:
|
||||
- GRADLE_OPTS="-XX:MaxPermSize=256m"
|
||||
|
||||
android:
|
||||
components:
|
||||
# Uncomment the lines below if you want to
|
||||
@ -7,7 +15,7 @@ android:
|
||||
- tools
|
||||
|
||||
# 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
|
||||
- android-23
|
||||
|
88
CHANGELOG.md
88
CHANGELOG.md
@ -1,4 +1,92 @@
|
||||
###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
|
||||
* Pebble: Report connection state to PebbleKit companion apps via content provider. NOTE: Makes Gadgetbridge mutual exclusive with the original Pebble app.
|
||||
|
21
README.md
21
README.md
@ -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)
|
||||
|
||||
## Supported Devices
|
||||
* Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, Pebble Time Round
|
||||
* Mi Band, Mi Band 1A, Mi Band 1S (experimental)
|
||||
|
||||
## Features (Pebble)
|
||||
|
||||
* 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 language files (.pbl)
|
||||
* Take and share screenshots from the Pebble's screen
|
||||
* PebbleKit support for 3rd Party Android Apps support (experimental)
|
||||
* Morpheuz sleep data syncronization (experimental)
|
||||
* Misfit steps data synchronization (experimental)
|
||||
* PebbleKit support for 3rd Party Android Apps (experimental)
|
||||
* Fetch activity data from Pebble Health, Misfit and Morpheuz (experimental)
|
||||
* Configure watchfaces / apps (limited compatibility, experimental)
|
||||
|
||||
## 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.
|
||||
|
||||
## 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
|
||||
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)
|
||||
|
||||
* 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
|
||||
* Synchronize the time to the Mi Band
|
||||
* Display firmware version and battery state
|
||||
* Firmware Update
|
||||
* Heartrate Measurement (alpha)
|
||||
* Synchronize activity data
|
||||
* Display sleep data (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);
|
||||
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
|
||||
manually.
|
||||
|
||||
|
||||
## Having problems?
|
||||
|
||||
1. Open Gadgetbridge's settings and check the option to write log files
|
||||
|
@ -4,9 +4,11 @@ apply plugin: 'pmd'
|
||||
|
||||
def ABORT_ON_CHECK_FAILURE=false
|
||||
|
||||
tasks.withType(Test) { systemProperty 'MiFirmwareDir', System.getProperty('MiFirmwareDir', null) }
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.2"
|
||||
buildToolsVersion "23.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "nodomain.freeyourgadget.gadgetbridge"
|
||||
@ -14,8 +16,8 @@ android {
|
||||
targetSdkVersion 23
|
||||
|
||||
// note: always bump BOTH versionCode and versionName!
|
||||
versionName "0.7.3"
|
||||
versionCode 39
|
||||
versionName "0.9.7"
|
||||
versionCode 51
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
@ -44,12 +46,14 @@ dependencies {
|
||||
testCompile "org.mockito:mockito-core:1.9.5"
|
||||
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:appcompat-v7:23.1.1'
|
||||
compile 'com.android.support:support-v4:23.1.1'
|
||||
compile 'com.android.support:appcompat-v7:23.3.0'
|
||||
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 '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 'de.cketti.library.changelog:ckchangelog:1.2.2'
|
||||
}
|
||||
|
||||
check.dependsOn 'findbugs', 'pmd', 'lint'
|
||||
|
@ -24,7 +24,6 @@
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth_le"
|
||||
android:required="false" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony"
|
||||
android:required="false" />
|
||||
@ -54,6 +53,7 @@
|
||||
android:label="@string/preferences_miband_settings"
|
||||
android:parentActivityName=".activities.SettingsActivity" />
|
||||
<activity
|
||||
android:launchMode="singleTop"
|
||||
android:name=".activities.AppManagerActivity"
|
||||
android:label="@string/title_activity_appmanager"
|
||||
android:parentActivityName=".activities.ControlCenter" />
|
||||
@ -67,6 +67,7 @@
|
||||
android:parentActivityName=".activities.ControlCenter">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="*/*" />
|
||||
@ -87,7 +88,6 @@
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
|
||||
|
||||
<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="/.*\\.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="/.*\\.pbl" />
|
||||
<data android:pathPattern="/.*\\..*\\.pbl" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.pbl" />
|
||||
@ -123,6 +121,7 @@
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<!-- no mimeType filter, needed for CM-derived ROMs? -->
|
||||
@ -142,7 +141,6 @@
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.fw" />
|
||||
|
||||
<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="/.*\\.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="/.*\\.pbl" />
|
||||
<data android:pathPattern="/.*\\..*\\.pbl" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.pbl" />
|
||||
@ -180,10 +176,11 @@
|
||||
<!-- to receive the firmwares from the donwload content provider -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="application/octet-stream" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<service
|
||||
@ -237,11 +234,11 @@
|
||||
android:name=".activities.DebugActivity"
|
||||
android:label="@string/title_activity_debug"
|
||||
android:parentActivityName=".activities.ControlCenter"
|
||||
android:windowSoftInputMode="stateHidden"></activity>
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name=".activities.DiscoveryActivity"
|
||||
android:label="@string/title_activity_discovery"
|
||||
android:parentActivityName=".activities.ControlCenter"></activity>
|
||||
android:parentActivityName=".activities.ControlCenter" />
|
||||
<activity
|
||||
android:name=".activities.AndroidPairingActivity"
|
||||
android:label="@string/title_activity_android_pairing" />
|
||||
@ -260,7 +257,38 @@
|
||||
android:name=".activities.AlarmDetails"
|
||||
android:label="@string/title_activity_alarm_details"
|
||||
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>
|
||||
|
||||
</manifest>
|
||||
|
56
app/src/main/assets/app_config/configure.html
Normal file
56
app/src/main/assets/app_config/configure.html
Normal 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>
|
458
app/src/main/assets/app_config/js/Uri.js
Normal file
458
app/src/main/assets/app_config/js/Uri.js
Normal 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));
|
137
app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js
Normal file
137
app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js
Normal 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();
|
||||
}
|
||||
});
|
||||
}
|
149
app/src/main/assets/ic_launcher.svg
Normal file
149
app/src/main/assets/ic_launcher.svg
Normal 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 |
@ -15,15 +15,19 @@
|
||||
<!-- encoders are by default assigned the type
|
||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${GB_LOGFILES_DIR}/gadgetbridge-%d{yyyy-MM-dd}.log.zip</fileNamePattern>
|
||||
<fileNamePattern>${GB_LOGFILES_DIR}/gadgetbridge-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern>
|
||||
<maxHistory>10</maxHistory>
|
||||
<timeBasedFileNamingAndTriggeringPolicy
|
||||
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<!-- or whenever the file size reaches 50MB -->
|
||||
<maxFileSize>2MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
|
||||
<maxFileSize>5MB</maxFileSize>
|
||||
</triggeringPolicy>
|
||||
<encoder>
|
||||
<!--<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>-->
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{1} - %msg%n</pattern>
|
||||
<!-- to debug crashes, set immediateFlush to true, otherwise keep it false to improve throughput -->
|
||||
<immediateFlush>false</immediateFlush>
|
||||
<!--<pattern>%date [%thread] %-5level %logger{25} - %msg%n</pattern>-->
|
||||
</encoder>
|
||||
</appender>
|
||||
|
@ -6,11 +6,13 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -22,14 +24,19 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
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.DBConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
//import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver;
|
||||
|
||||
@ -45,7 +52,13 @@ public class GBApplication extends Application {
|
||||
private static final Lock dbLock = new ReentrantLock();
|
||||
private static DeviceService deviceService;
|
||||
private static SharedPreferences sharedPrefs;
|
||||
private static final String PREFS_VERSION = "shared_preferences_version";
|
||||
//if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version
|
||||
private static final int CURRENT_PREFS_VERSION = 2;
|
||||
private static LimitedQueue mIDSenderLookup = new LimitedQueue(16);
|
||||
private static Appender<ILoggingEvent> fileLogger;
|
||||
private static Prefs prefs;
|
||||
private static GBPrefs gbPrefs;
|
||||
|
||||
public static final String ACTION_QUIT
|
||||
= "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit";
|
||||
@ -79,10 +92,16 @@ public class GBApplication extends Application {
|
||||
super.onCreate();
|
||||
|
||||
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs = new Prefs(sharedPrefs);
|
||||
gbPrefs = new GBPrefs(prefs);
|
||||
|
||||
// don't do anything here before we set up logging, otherwise
|
||||
// slf4j may be implicitly initialized before we properly configured it.
|
||||
setupLogging();
|
||||
setupLogging(isFileLoggingEnabled());
|
||||
|
||||
if (getPrefsFileVersion() != CURRENT_PREFS_VERSION) {
|
||||
migratePrefs(getPrefsFileVersion());
|
||||
}
|
||||
|
||||
setupExceptionHandler();
|
||||
// For debugging problems with the logback configuration
|
||||
@ -111,35 +130,71 @@ public class GBApplication extends Application {
|
||||
}
|
||||
|
||||
public static boolean isFileLoggingEnabled() {
|
||||
return sharedPrefs.getBoolean("log_to_file", false);
|
||||
return prefs.getBoolean("log_to_file", false);
|
||||
}
|
||||
|
||||
private void setupLogging() {
|
||||
if (isFileLoggingEnabled()) {
|
||||
public static void setupLogging(boolean enable) {
|
||||
try {
|
||||
if (fileLogger == null) {
|
||||
File dir = FileUtils.getExternalFilesDir();
|
||||
// used by assets/logback.xml since the location cannot be statically determined
|
||||
System.setProperty("GB_LOGFILES_DIR", dir.getAbsolutePath());
|
||||
rememberFileLogger();
|
||||
}
|
||||
if (enable) {
|
||||
startFileLogger();
|
||||
} else {
|
||||
stopFileLogger();
|
||||
}
|
||||
getLogger().info("Gadgetbridge version: " + BuildConfig.VERSION_NAME);
|
||||
} catch (IOException ex) {
|
||||
Log.e("GBApplication", "External files dir not available, cannot log to file", ex);
|
||||
removeFileLogger();
|
||||
}
|
||||
} else {
|
||||
removeFileLogger();
|
||||
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 {
|
||||
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) {
|
||||
Log.e("GBApplication", "Error removing logger FILE appender", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Logger getLogger() {
|
||||
private static Logger getLogger() {
|
||||
return LoggerFactory.getLogger(GBApplication.class);
|
||||
}
|
||||
|
||||
@ -188,10 +243,6 @@ public class GBApplication extends Application {
|
||||
dbLock.unlock();
|
||||
}
|
||||
|
||||
public static boolean isRunningOnKitkatOrLater() {
|
||||
return VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||
}
|
||||
|
||||
public static boolean isRunningLollipopOrLater() {
|
||||
return VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
|
||||
}
|
||||
@ -242,7 +293,84 @@ public class GBApplication extends Application {
|
||||
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() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
|
||||
@ -18,7 +17,7 @@ import android.support.v4.app.FragmentPagerAdapter;
|
||||
*
|
||||
* @see AbstractGBFragment
|
||||
*/
|
||||
public abstract class AbstractGBFragmentActivity extends FragmentActivity {
|
||||
public abstract class AbstractGBFragmentActivity extends GBActivity {
|
||||
/**
|
||||
* The {@link android.support.v4.view.PagerAdapter} that will provide
|
||||
* fragments for each of the sections. We use a
|
||||
|
@ -1,16 +1,30 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.Nullable;
|
||||
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.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
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.
|
||||
* 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 {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractSettingsActivity.class);
|
||||
private AppCompatDelegate delegate;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
@Override
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void updateSummary(Preference preference, Object value) {
|
||||
String stringValue = value.toString();
|
||||
String stringValue = String.valueOf(value);
|
||||
|
||||
if (preference instanceof ListPreference) {
|
||||
// For list preferences, look up the correct display value in
|
||||
@ -56,15 +79,15 @@ public abstract class AbstractSettingsActivity extends PreferenceActivity {
|
||||
}
|
||||
|
||||
private static class ExtraSetSummaryOnChangeListener extends SimpleSetSummaryOnChangeListener {
|
||||
private final Preference.OnPreferenceChangeListener delegate;
|
||||
private final Preference.OnPreferenceChangeListener prefChangeListener;
|
||||
|
||||
public ExtraSetSummaryOnChangeListener(Preference.OnPreferenceChangeListener delegate) {
|
||||
this.delegate = delegate;
|
||||
public ExtraSetSummaryOnChangeListener(Preference.OnPreferenceChangeListener prefChangeListener) {
|
||||
this.prefChangeListener = prefChangeListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object value) {
|
||||
boolean result = delegate.onPreferenceChange(preference, value);
|
||||
boolean result = prefChangeListener.onPreferenceChange(preference, value);
|
||||
if (result) {
|
||||
return super.onPreferenceChange(preference, value);
|
||||
}
|
||||
@ -74,11 +97,22 @@ public abstract class AbstractSettingsActivity extends PreferenceActivity {
|
||||
|
||||
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
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getDelegate().onPostCreate(savedInstanceState);
|
||||
|
||||
for (String prefKey : getPreferenceKeysWithSummary()) {
|
||||
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
|
||||
* 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.format.DateFormat;
|
||||
@ -12,7 +11,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
|
||||
|
||||
public class AlarmDetails extends Activity {
|
||||
public class AlarmDetails extends GBActivity {
|
||||
|
||||
private GBAlarm alarm;
|
||||
private TimePicker timePicker;
|
||||
|
@ -1,11 +1,10 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class AndroidPairingActivity extends Activity {
|
||||
public class AndroidPairingActivity extends GBActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -1,15 +1,12 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.view.LayoutInflater;
|
||||
@ -28,13 +25,14 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
|
||||
public class AppBlacklistActivity extends Activity {
|
||||
public class AppBlacklistActivity extends GBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AppBlacklistActivity.class);
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@ -47,20 +45,41 @@ public class AppBlacklistActivity extends Activity {
|
||||
}
|
||||
};
|
||||
|
||||
private SharedPreferences sharedPrefs;
|
||||
private IdentityHashMap<ApplicationInfo, String> nameMap;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_appblacklist);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
final PackageManager pm = getPackageManager();
|
||||
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
|
||||
final List<ApplicationInfo> packageList = pm.getInstalledApplications(PackageManager.GET_META_DATA);
|
||||
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) {
|
||||
@Override
|
||||
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);
|
||||
|
||||
deviceAppVersionAuthorLabel.setText(appInfo.packageName);
|
||||
deviceAppNameLabel.setText(appInfo.loadLabel(pm));
|
||||
deviceAppNameLabel.setText(nameMap.get(appInfo));
|
||||
deviceImageView.setImageDrawable(appInfo.loadIcon(pm));
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
@ -1,14 +1,11 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.view.ContextMenu;
|
||||
@ -30,12 +27,15 @@ import java.util.UUID;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAppAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol;
|
||||
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
|
||||
= "nodomain.freeyourgadget.gadgetbridge.appmanager.action.refresh_applist";
|
||||
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));
|
||||
}
|
||||
|
||||
if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
|
||||
if (prefs.getBoolean("pebble_force_untested", false)) {
|
||||
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 GBDeviceAppAdapter mGBDeviceAppAdapter;
|
||||
private GBDeviceApp selectedApp = null;
|
||||
private GBDevice mGBDevice = null;
|
||||
|
||||
private List<GBDeviceApp> getSystemApps() {
|
||||
List<GBDeviceApp> systemApps = new ArrayList<>();
|
||||
systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (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;
|
||||
}
|
||||
@ -97,11 +100,14 @@ public class AppManagerActivity extends Activity {
|
||||
for (File file : files) {
|
||||
if (file.getName().endsWith(".pbw")) {
|
||||
String baseName = file.getName().substring(0, file.getName().length() - 4);
|
||||
//metadata
|
||||
File jsonFile = new File(cachePath, baseName + ".json");
|
||||
//configuration
|
||||
File configFile = new File(cachePath, baseName + "_config.js");
|
||||
try {
|
||||
String jsonstring = FileUtils.getStringFromFile(jsonFile);
|
||||
JSONObject json = new JSONObject(jsonstring);
|
||||
cachedAppList.add(new GBDeviceApp(json));
|
||||
cachedAppList.add(new GBDeviceApp(json, configFile.exists()));
|
||||
} catch (Exception 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));
|
||||
@ -116,10 +122,16 @@ public class AppManagerActivity extends Activity {
|
||||
protected void onCreate(Bundle 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);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
ListView appListView = (ListView) findViewById(R.id.appListView);
|
||||
mGBDeviceAppAdapter = new GBDeviceAppAdapter(this, appList);
|
||||
@ -137,7 +149,7 @@ public class AppManagerActivity extends Activity {
|
||||
|
||||
appList.addAll(getCachedApps());
|
||||
|
||||
if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
|
||||
if (prefs.getBoolean("pebble_force_untested", false)) {
|
||||
appList.addAll(getSystemApps());
|
||||
}
|
||||
|
||||
@ -157,24 +169,29 @@ public class AppManagerActivity extends Activity {
|
||||
AdapterView.AdapterContextMenuInfo acmi = (AdapterView.AdapterContextMenuInfo) menuInfo;
|
||||
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);
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.appmanager_health_deactivate:
|
||||
case R.id.appmanager_app_delete:
|
||||
GBApplication.deviceService().onAppDelete(selectedApp.getUUID());
|
||||
return true;
|
||||
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;
|
||||
try {
|
||||
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));
|
||||
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:
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.ListActivity;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.ListView;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
@ -16,11 +14,12 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.GBAlarmListAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
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;
|
||||
|
||||
@ -33,19 +32,19 @@ public class ConfigureAlarms extends ListActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_configure_alarms);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
preferencesAlarmListSet = sharedPrefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>());
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
preferencesAlarmListSet = prefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>());
|
||||
if (preferencesAlarmListSet.isEmpty()) {
|
||||
//initialize the preferences
|
||||
preferencesAlarmListSet = new HashSet<>(Arrays.asList(GBAlarm.DEFAULT_ALARMS));
|
||||
sharedPrefs.edit().putStringSet(PREF_MIBAND_ALARMS, preferencesAlarmListSet).apply();
|
||||
prefs.getPreferences().edit().putStringSet(PREF_MIBAND_ALARMS, preferencesAlarmListSet).apply();
|
||||
}
|
||||
|
||||
mGBAlarmListAdapter = new GBAlarmListAdapter(this, preferencesAlarmListSet);
|
||||
|
||||
setListAdapter(mGBAlarmListAdapter);
|
||||
ListView listView = (ListView) findViewById(R.id.alarm_list);
|
||||
listView.setAdapter(mGBAlarmListAdapter);
|
||||
updateAlarmsFromPrefs();
|
||||
}
|
||||
|
||||
@ -66,9 +65,9 @@ public class ConfigureAlarms extends ListActivity {
|
||||
}
|
||||
|
||||
private void updateAlarmsFromPrefs() {
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
preferencesAlarmListSet = sharedPrefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>());
|
||||
int reservedSlots = Integer.parseInt(sharedPrefs.getString(MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, "0"));
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
preferencesAlarmListSet = prefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>());
|
||||
int reservedSlots = prefs.getInt(MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, 0);
|
||||
|
||||
mGBAlarmListAdapter.setAlarmList(preferencesAlarmListSet, reservedSlots);
|
||||
mGBAlarmListAdapter.notifyDataSetChanged();
|
||||
|
@ -1,5 +1,7 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
@ -8,9 +10,12 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
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.widget.SwipeRefreshLayout;
|
||||
import android.view.ContextMenu;
|
||||
@ -18,6 +23,7 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
@ -29,6 +35,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import de.cketti.library.changelog.ChangeLog;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
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.util.DeviceHelper;
|
||||
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);
|
||||
|
||||
@ -46,6 +54,9 @@ public class ControlCenter extends Activity {
|
||||
= "nodomain.freeyourgadget.gadgetbridge.controlcenter.action.set_version";
|
||||
|
||||
private TextView hintTextView;
|
||||
private FloatingActionButton fab;
|
||||
private ImageView background;
|
||||
|
||||
private SwipeRefreshLayout swipeLayout;
|
||||
private GBDeviceAdapter mGBDeviceAdapter;
|
||||
private GBDevice selectedDevice = null;
|
||||
@ -116,15 +127,26 @@ public class ControlCenter extends Activity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_controlcenter);
|
||||
|
||||
hintTextView = (TextView) findViewById(R.id.hintTextView);
|
||||
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);
|
||||
deviceListView.setAdapter(this.mGBDeviceAdapter);
|
||||
deviceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView parent, View v, int position, long id) {
|
||||
GBDevice gbDevice = deviceList.get(position);
|
||||
if (gbDevice.isConnected()) {
|
||||
if (gbDevice.isInitialized()) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
|
||||
Class<? extends Activity> primaryActivity = coordinator.getPrimaryActivity();
|
||||
if (primaryActivity != null) {
|
||||
@ -161,17 +183,25 @@ public class ControlCenter extends Activity {
|
||||
/*
|
||||
* Ask for permission to intercept notifications on first run.
|
||||
*/
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (sharedPrefs.getBoolean("firstrun", true)) {
|
||||
sharedPrefs.edit().putBoolean("firstrun", false).apply();
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (prefs.getBoolean("firstrun", true)) {
|
||||
prefs.getPreferences().edit().putBoolean("firstrun", false).apply();
|
||||
Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
|
||||
startActivity(enableIntent);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
checkAndRequestPermissions();
|
||||
}
|
||||
|
||||
ChangeLog cl = new ChangeLog(this);
|
||||
if (cl.isFirstRun()) {
|
||||
cl.getLogDialog().show();
|
||||
}
|
||||
|
||||
GBApplication.deviceService().start();
|
||||
|
||||
enableSwipeRefresh(selectedDevice);
|
||||
if (GB.isBluetoothEnabled() && deviceList.isEmpty()) {
|
||||
// start discovery when no devices are present
|
||||
if (GB.isBluetoothEnabled() && deviceList.isEmpty() && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
startActivity(new Intent(this, DiscoveryActivity.class));
|
||||
} else {
|
||||
GBApplication.deviceService().requestDeviceInfo();
|
||||
@ -196,6 +226,9 @@ public class ControlCenter extends Activity {
|
||||
if (!coordinator.supportsScreenshots()) {
|
||||
menu.removeItem(R.id.controlcenter_take_screenshot);
|
||||
}
|
||||
if (!coordinator.supportsAlarmConfiguration()) {
|
||||
menu.removeItem(R.id.controlcenter_configure_alarms);
|
||||
}
|
||||
|
||||
if (selectedDevice.getState() == GBDevice.State.NOT_CONNECTED) {
|
||||
menu.removeItem(R.id.controlcenter_disconnect);
|
||||
@ -314,15 +347,19 @@ public class ControlCenter extends Activity {
|
||||
Intent quitIntent = new Intent(GBApplication.ACTION_QUIT);
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(quitIntent);
|
||||
return true;
|
||||
case R.id.action_discover:
|
||||
Intent discoverIntent = new Intent(this, DiscoveryActivity.class);
|
||||
startActivity(discoverIntent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
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
|
||||
protected void onDestroy() {
|
||||
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) {
|
||||
hintTextView.setText(R.string.tap_connected_device_for_app_mananger);
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(selectedDevice);
|
||||
hintTextView.setText(coordinator.getTapString());
|
||||
} else if (!deviceList.isEmpty()) {
|
||||
hintTextView.setText(R.string.tap_a_device_to_connect);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
@ -14,6 +13,7 @@ import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
@ -29,14 +29,16 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.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.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
|
||||
public class DebugActivity extends Activity {
|
||||
public class DebugActivity extends GBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DebugActivity.class);
|
||||
|
||||
private static final String EXTRA_REPLY = "reply";
|
||||
@ -53,6 +55,7 @@ public class DebugActivity extends Activity {
|
||||
private Button setMusicInfoButton;
|
||||
private Button setTimeButton;
|
||||
private Button rebootButton;
|
||||
private Button HeartRateButton;
|
||||
private Button exportDBButton;
|
||||
private Button importDBButton;
|
||||
private Button deleteDBButton;
|
||||
@ -62,16 +65,23 @@ public class DebugActivity extends Activity {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case GBApplication.ACTION_QUIT:
|
||||
case GBApplication.ACTION_QUIT: {
|
||||
finish();
|
||||
break;
|
||||
case ACTION_REPLY:
|
||||
}
|
||||
case ACTION_REPLY: {
|
||||
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||
CharSequence reply = remoteInput.getCharSequence(EXTRA_REPLY);
|
||||
LOG.info("got wearable reply: " + reply);
|
||||
GB.toast(context, "got wearable reply: " + reply, Toast.LENGTH_SHORT, GB.INFO);
|
||||
break;
|
||||
}
|
||||
case DeviceService.ACTION_HEARTRATE_MEASUREMENT: {
|
||||
int hrValue = intent.getIntExtra(DeviceService.EXTRA_HEART_RATE_VALUE, -1);
|
||||
GB.toast(DebugActivity.this, "Heart Rate measured: " + hrValue, Toast.LENGTH_LONG, GB.INFO);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -79,12 +89,13 @@ public class DebugActivity extends Activity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_debug);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
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);
|
||||
sendSMSButton = (Button) findViewById(R.id.sendSMSButton);
|
||||
@ -117,20 +128,20 @@ public class DebugActivity extends Activity {
|
||||
incomingCallButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
GBApplication.deviceService().onSetCallState(
|
||||
editContent.getText().toString(),
|
||||
null,
|
||||
ServiceCommand.CALL_INCOMING);
|
||||
CallSpec callSpec = new CallSpec();
|
||||
callSpec.command = CallSpec.CALL_INCOMING;
|
||||
callSpec.number = editContent.getText().toString();
|
||||
GBApplication.deviceService().onSetCallState(callSpec);
|
||||
}
|
||||
});
|
||||
outgoingCallButton = (Button) findViewById(R.id.outgoingCallButton);
|
||||
outgoingCallButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
GBApplication.deviceService().onSetCallState(
|
||||
editContent.getText().toString(),
|
||||
null,
|
||||
ServiceCommand.CALL_OUTGOING);
|
||||
CallSpec callSpec = new CallSpec();
|
||||
callSpec.command = CallSpec.CALL_OUTGOING;
|
||||
callSpec.number = editContent.getText().toString();
|
||||
GBApplication.deviceService().onSetCallState(callSpec);
|
||||
}
|
||||
});
|
||||
|
||||
@ -138,20 +149,18 @@ public class DebugActivity extends Activity {
|
||||
startCallButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
GBApplication.deviceService().onSetCallState(
|
||||
null,
|
||||
null,
|
||||
ServiceCommand.CALL_START);
|
||||
CallSpec callSpec = new CallSpec();
|
||||
callSpec.command = CallSpec.CALL_START;
|
||||
GBApplication.deviceService().onSetCallState(callSpec);
|
||||
}
|
||||
});
|
||||
endCallButton = (Button) findViewById(R.id.endCallButton);
|
||||
endCallButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
GBApplication.deviceService().onSetCallState(
|
||||
null,
|
||||
null,
|
||||
ServiceCommand.CALL_END);
|
||||
CallSpec callSpec = new CallSpec();
|
||||
callSpec.command = CallSpec.CALL_END;
|
||||
GBApplication.deviceService().onSetCallState(callSpec);
|
||||
}
|
||||
});
|
||||
|
||||
@ -185,15 +194,28 @@ public class DebugActivity extends Activity {
|
||||
GBApplication.deviceService().onReboot();
|
||||
}
|
||||
});
|
||||
HeartRateButton = (Button) findViewById(R.id.HearRateButton);
|
||||
HeartRateButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
GB.toast("Measuring heart rate, please wait...", Toast.LENGTH_LONG, GB.INFO);
|
||||
GBApplication.deviceService().onHeartRateTest();
|
||||
}
|
||||
});
|
||||
|
||||
setMusicInfoButton = (Button) findViewById(R.id.setMusicInfoButton);
|
||||
setMusicInfoButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
GBApplication.deviceService().onSetMusicInfo(
|
||||
editContent.getText().toString() + "(artist)",
|
||||
editContent.getText().toString() + "(album)",
|
||||
editContent.getText().toString() + "(tracl)");
|
||||
MusicSpec musicSpec = new MusicSpec();
|
||||
musicSpec.artist = editContent.getText().toString() + "(artist)";
|
||||
musicSpec.album = editContent.getText().toString() + "(album)";
|
||||
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
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
unregisterReceiver(mReceiver);
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
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 long SCAN_DURATION = 60000; // 60s
|
||||
|
||||
@ -249,7 +249,13 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
|
||||
|
||||
private void bluetoothStateChanged(int oldState, int newState) {
|
||||
discoveryFinished();
|
||||
startButton.setEnabled(newState == BluetoothAdapter.STATE_ON);
|
||||
if (newState == BluetoothAdapter.STATE_ON) {
|
||||
this.adapter = BluetoothAdapter.getDefaultAdapter();
|
||||
startButton.setEnabled(true);
|
||||
} else {
|
||||
this.adapter = null;
|
||||
startButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void discoveryFinished() {
|
||||
@ -284,8 +290,15 @@ public class DiscoveryActivity extends Activity implements AdapterView.OnItemCli
|
||||
return false;
|
||||
}
|
||||
BluetoothAdapter adapter = bluetoothService.getAdapter();
|
||||
if (adapter == null) {
|
||||
LOG.warn("No bluetooth available");
|
||||
this.adapter = null;
|
||||
return false;
|
||||
}
|
||||
if (!adapter.isEnabled()) {
|
||||
LOG.warn("Bluetooth not enabled");
|
||||
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
|
||||
startActivity(enableBtIntent);
|
||||
this.adapter = null;
|
||||
return false;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
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.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
|
||||
public class FwAppInstallerActivity extends Activity implements InstallActivity {
|
||||
public class FwAppInstallerActivity extends GBActivity implements InstallActivity {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FwAppInstallerActivity.class);
|
||||
private static final String ITEM_DETAILS = "details";
|
||||
|
||||
private TextView fwAppInstallTextView;
|
||||
private Button installButton;
|
||||
@ -45,13 +46,22 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
|
||||
private InstallHandler installHandler;
|
||||
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() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(GBApplication.ACTION_QUIT)) {
|
||||
if (GBApplication.ACTION_QUIT.equals(action)) {
|
||||
finish();
|
||||
} else if (action.equals(GBDevice.ACTION_DEVICE_CHANGED)) {
|
||||
} else if (GBDevice.ACTION_DEVICE_CHANGED.equals(action)) {
|
||||
device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
if (device != null) {
|
||||
refreshBusyState(device);
|
||||
@ -67,13 +77,13 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
|
||||
validateInstallation();
|
||||
}
|
||||
}
|
||||
} else if (GB.ACTION_DISPLAY_MESSAGE.equals(action)) {
|
||||
String message = intent.getStringExtra(GB.DISPLAY_MESSAGE_MESSAGE);
|
||||
int severity = intent.getIntExtra(GB.DISPLAY_MESSAGE_SEVERITY, GB.INFO);
|
||||
addMessage(message, severity);
|
||||
}
|
||||
}
|
||||
};
|
||||
private ProgressBar mProgressBar;
|
||||
private ListView itemListView;
|
||||
private final List<ItemWithDetails> mItems = new ArrayList<>();
|
||||
private ItemWithDetailsAdapter mItemAdapter;
|
||||
|
||||
private void refreshBusyState(GBDevice dev) {
|
||||
if (dev.isConnecting() || dev.isBusy()) {
|
||||
@ -102,11 +112,18 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_appinstaller);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
GBDevice dev = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
if (dev != null) {
|
||||
device = dev;
|
||||
}
|
||||
if (savedInstanceState != null) {
|
||||
mDetails = savedInstanceState.getParcelableArrayList(ITEM_DETAILS);
|
||||
if (mDetails == null) {
|
||||
mDetails = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
mayConnect = true;
|
||||
itemListView = (ListView) findViewById(R.id.itemListView);
|
||||
mItemAdapter = new ItemWithDetailsAdapter(this, mItems);
|
||||
@ -114,10 +131,15 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
|
||||
fwAppInstallTextView = (TextView) findViewById(R.id.infoTextView);
|
||||
installButton = (Button) findViewById(R.id.installButton);
|
||||
mProgressBar = (ProgressBar) findViewById(R.id.installProgressBar);
|
||||
detailsListView = (ListView) findViewById(R.id.detailsListView);
|
||||
mDetailsItemAdapter = new ItemWithDetailsAdapter(this, mDetails);
|
||||
mDetailsItemAdapter.setSize(ItemWithDetailsAdapter.SIZE_SMALL);
|
||||
detailsListView.setAdapter(mDetailsItemAdapter);
|
||||
setInstallEnabled(false);
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
filter.addAction(GB.ACTION_DISPLAY_MESSAGE);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
|
||||
installButton.setOnClickListener(new View.OnClickListener() {
|
||||
@ -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) {
|
||||
for (DeviceCoordinator coordinator : DeviceHelper.getInstance().getAllCoordinators()) {
|
||||
InstallHandler handler = coordinator.findInstallHandler(uri, this);
|
||||
@ -195,4 +223,9 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
|
||||
mItems.add(item);
|
||||
mItemAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void addMessage(String message, int severity) {
|
||||
mDetails.add(new GenericItem(message));
|
||||
mDetailsItemAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -7,11 +7,22 @@ import android.os.Bundle;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
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 {
|
||||
@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
|
||||
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
||||
|
||||
@ -103,10 +136,6 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
@Override
|
||||
protected String[] getPreferenceKeysWithSummary() {
|
||||
return new String[]{
|
||||
"audio_player",
|
||||
"notification_mode_calls",
|
||||
"notification_mode_sms",
|
||||
"notification_mode_k9mail",
|
||||
"pebble_emu_addr",
|
||||
"pebble_emu_port",
|
||||
"pebble_reconnect_attempts",
|
||||
@ -127,6 +156,10 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
"canned_reply_14",
|
||||
"canned_reply_15",
|
||||
"canned_reply_16",
|
||||
PREF_USER_YEAR_OF_BIRTH,
|
||||
PREF_USER_HEIGHT_CM,
|
||||
PREF_USER_WEIGHT_KG,
|
||||
PREF_USER_SLEEP_DURATION,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
@ -12,9 +13,15 @@ import android.view.View;
|
||||
|
||||
import com.github.mikephil.charting.charts.BarLineChartBase;
|
||||
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.BarDataSet;
|
||||
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.LoggerFactory;
|
||||
@ -23,14 +30,17 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
@ -72,6 +82,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
}
|
||||
};
|
||||
private boolean mChartDirty = true;
|
||||
private boolean supportsHeartrateChart = true;
|
||||
private AsyncTask refreshTask;
|
||||
|
||||
public boolean isChartDirty() {
|
||||
return mChartDirty;
|
||||
@ -79,6 +91,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
|
||||
public abstract String getTitle();
|
||||
|
||||
public boolean supportsHeartrate() {
|
||||
return supportsHeartrateChart;
|
||||
}
|
||||
|
||||
protected static final class ActivityConfig {
|
||||
public final int type;
|
||||
public final String label;
|
||||
@ -101,11 +117,15 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
protected int DESCRIPTION_COLOR;
|
||||
protected int CHART_TEXT_COLOR;
|
||||
protected int LEGEND_TEXT_COLOR;
|
||||
protected int HEARTRATE_COLOR;
|
||||
protected int HEARTRATE_FILL_COLOR;
|
||||
protected int AK_ACTIVITY_COLOR;
|
||||
protected int AK_DEEP_SLEEP_COLOR;
|
||||
protected int AK_LIGHT_SLEEP_COLOR;
|
||||
protected int AK_NOT_WORN_COLOR;
|
||||
|
||||
protected String HEARTRATE_LABEL;
|
||||
|
||||
protected AbstractChartFragment(String... intentFilterActions) {
|
||||
mIntentFilterActions = new HashSet<>();
|
||||
if (intentFilterActions != null) {
|
||||
@ -130,15 +150,18 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
BACKGROUND_COLOR = getResources().getColor(R.color.background_material_light);
|
||||
DESCRIPTION_COLOR = getResources().getColor(R.color.primarytext);
|
||||
BACKGROUND_COLOR = GBApplication.getBackgroundColor(getContext());
|
||||
LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(getContext());
|
||||
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_DEEP_SLEEP_COLOR = getResources().getColor(R.color.chart_light_sleep_light);
|
||||
AK_LIGHT_SLEEP_COLOR = getResources().getColor(R.color.chart_deep_sleep_light);
|
||||
AK_NOT_WORN_COLOR = getResources().getColor(R.color.chart_not_worn_light);
|
||||
|
||||
HEARTRATE_LABEL = getContext().getString(R.string.charts_legend_heartrate);
|
||||
|
||||
akActivity = new ActivityConfig(ActivityKind.TYPE_ACTIVITY, getString(R.string.abstract_chart_fragment_kind_activity), AK_ACTIVITY_COLOR);
|
||||
akLightSleep = new ActivityConfig(ActivityKind.TYPE_LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR);
|
||||
akDeepSleep = new ActivityConfig(ActivityKind.TYPE_DEEP_SLEEP, getString(R.string.abstract_chart_fragment_kind_deep_sleep), AK_DEEP_SLEEP_COLOR);
|
||||
@ -315,6 +338,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
}
|
||||
|
||||
protected void configureChartDefaults(Chart<?> chart) {
|
||||
chart.setDescription("");
|
||||
|
||||
// if enabled, the chart will always start at zero on the y-axis
|
||||
chart.setNoDataText(getString(R.string.chart_no_data_synchronize));
|
||||
|
||||
@ -323,6 +348,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
|
||||
// enable touch gestures
|
||||
chart.setTouchEnabled(true);
|
||||
|
||||
setupLegend(chart);
|
||||
}
|
||||
|
||||
protected void configureBarLineChartDefaults(BarLineChartBase<?> chart) {
|
||||
@ -349,7 +376,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
if (chartsHost.getDevice() != null) {
|
||||
mChartDirty = false;
|
||||
updateDateInfo(getStartDate(), getEndDate());
|
||||
createRefreshTask("Visualizing data", getActivity()).execute();
|
||||
if (refreshTask != null && refreshTask.getStatus() != AsyncTask.Status.FINISHED) {
|
||||
refreshTask.cancel(true);
|
||||
}
|
||||
refreshTask = createRefreshTask("Visualizing data", getActivity()).execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -357,9 +387,9 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
/**
|
||||
* This method reads the data from the database, analyzes and prepares it for
|
||||
* the charts. This will be called from a background task, so there must not be
|
||||
* any UI access. #renderCharts will be automatically called after this method.
|
||||
* any UI access. #updateChartsInUIThread and #renderCharts will be automatically called after this method.
|
||||
*/
|
||||
protected abstract void refreshInBackground(DBHandler db, GBDevice device);
|
||||
protected abstract ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device);
|
||||
|
||||
/**
|
||||
* Triggers the actual (re-) rendering of the chart.
|
||||
@ -367,7 +397,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
*/
|
||||
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();
|
||||
cal.clear();
|
||||
Date date;
|
||||
@ -375,11 +405,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
String dateStringTo = "";
|
||||
|
||||
LOG.info("" + getTitle() + ": number of samples:" + samples.size());
|
||||
CombinedData combinedData;
|
||||
if (samples.size() > 1) {
|
||||
float movement_divisor;
|
||||
boolean annotate = true;
|
||||
boolean use_steps_as_movement;
|
||||
SampleProvider provider = getProvider(gbDevice);
|
||||
|
||||
int last_type = ActivityKind.TYPE_UNKNOWN;
|
||||
|
||||
@ -389,7 +418,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
int numEntries = samples.size();
|
||||
List<String> xLabels = 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...
|
||||
int lastHrSampleIndex = -1;
|
||||
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
ActivitySample sample = samples.get(i);
|
||||
@ -431,6 +463,15 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
colors.add(akActivity.color);
|
||||
}
|
||||
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 = "";
|
||||
if (annotate) {
|
||||
@ -460,25 +501,34 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
xLabels.add(xLabel);
|
||||
}
|
||||
|
||||
chart.getXAxis().setValues(xLabels);
|
||||
// chart.getXAxis().setValues(xLabels);
|
||||
|
||||
BarDataSet activitySet = createActivitySet(activityEntries, colors, "Activity");
|
||||
|
||||
ArrayList<BarDataSet> dataSets = new ArrayList<>();
|
||||
dataSets.add(activitySet);
|
||||
|
||||
// create a data object with the datasets
|
||||
BarData data = new BarData(xLabels, dataSets);
|
||||
data.setGroupSpace(0);
|
||||
combinedData = new CombinedData(xLabels);
|
||||
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.setDescriptionPosition(?, ?);
|
||||
|
||||
setupLegend(chart);
|
||||
|
||||
chart.setData(data);
|
||||
} else {
|
||||
combinedData = new CombinedData(Collections.<String>emptyList());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
protected Entry createLineEntry(float value, int index) {
|
||||
return new Entry(value, index);
|
||||
}
|
||||
|
||||
protected BarDataSet createActivitySet(List<BarEntry> values, List<Integer> colors, String label) {
|
||||
BarDataSet set1 = new BarDataSet(values, label);
|
||||
set1.setColors(colors);
|
||||
@ -512,6 +566,27 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
// set1.setHighLightColor(Color.rgb(128, 0, 255));
|
||||
// set1.setColor(Color.rgb(89, 178, 44));
|
||||
set1.setValueTextColor(CHART_TEXT_COLOR);
|
||||
set1.setAxisDependency(YAxis.AxisDependency.LEFT);
|
||||
return set1;
|
||||
}
|
||||
|
||||
protected LineDataSet createHeartrateSet(List<Entry> values, String label) {
|
||||
LineDataSet set1 = new LineDataSet(values, label);
|
||||
set1.setLineWidth(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;
|
||||
}
|
||||
|
||||
@ -554,6 +629,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
}
|
||||
|
||||
public class RefreshTask extends DBAccess {
|
||||
private ChartsData chartsData;
|
||||
|
||||
public RefreshTask(String task, Context context) {
|
||||
super(task, context);
|
||||
}
|
||||
@ -562,7 +639,9 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
protected void doInBackground(DBHandler db) {
|
||||
ChartsHost chartsHost = getChartsHost();
|
||||
if (chartsHost != null) {
|
||||
refreshInBackground(db, chartsHost.getDevice());
|
||||
chartsData = refreshInBackground(chartsHost, db, chartsHost.getDevice());
|
||||
} else {
|
||||
cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -571,6 +650,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
super.onPostExecute(o);
|
||||
FragmentActivity activity = getActivity();
|
||||
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
|
||||
updateChartsnUIThread(chartsData);
|
||||
renderCharts();
|
||||
} else {
|
||||
LOG.info("Not rendering charts because activity is not available anymore");
|
||||
@ -578,6 +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
|
||||
* 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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
@ -70,6 +71,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
|
||||
// y.setDrawLabels(false);
|
||||
// TODO: make fixed max value optional
|
||||
y.setAxisMaxValue(1f);
|
||||
y.setAxisMinValue(0);
|
||||
y.setDrawTopYLabelEntry(false);
|
||||
y.setTextColor(CHART_TEXT_COLOR);
|
||||
|
||||
@ -78,10 +80,12 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
|
||||
|
||||
YAxis yAxisRight = mChart.getAxisRight();
|
||||
yAxisRight.setDrawGridLines(false);
|
||||
yAxisRight.setEnabled(false);
|
||||
yAxisRight.setDrawLabels(false);
|
||||
yAxisRight.setDrawTopYLabelEntry(false);
|
||||
yAxisRight.setEnabled(supportsHeartrate());
|
||||
yAxisRight.setDrawLabels(true);
|
||||
yAxisRight.setDrawTopYLabelEntry(true);
|
||||
yAxisRight.setTextColor(CHART_TEXT_COLOR);
|
||||
yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE);
|
||||
yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE);
|
||||
|
||||
// refresh immediately instead of use refreshIfVisible(), for perceived performance
|
||||
refresh();
|
||||
@ -103,11 +107,16 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refreshInBackground(DBHandler db, GBDevice device) {
|
||||
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice 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.setData(dcd.getCombinedData());
|
||||
}
|
||||
|
||||
protected void renderCharts() {
|
||||
@ -125,6 +134,10 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
|
||||
legendLabels.add(akDeepSleep.label);
|
||||
legendColors.add(akNotWorn.color);
|
||||
legendLabels.add(akNotWorn.label);
|
||||
if (supportsHeartrate()) {
|
||||
legendColors.add(HEARTRATE_COLOR);
|
||||
legendLabels.add(HEARTRATE_LABEL);
|
||||
}
|
||||
chart.getLegend().setCustom(legendColors, legendLabels);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,4 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||
|
||||
public abstract class ChartsData {
|
||||
}
|
@ -49,6 +49,7 @@ public class CustomBarChart extends BarChart {
|
||||
/**
|
||||
* Call this to set the next value for the Entry to be animated.
|
||||
* Call animateY() when ready to do that.
|
||||
*
|
||||
* @param nextValue
|
||||
*/
|
||||
public void setSingleEntryYValue(float nextValue) {
|
||||
|
@ -23,6 +23,7 @@ import com.github.mikephil.charting.components.YAxis;
|
||||
import com.github.mikephil.charting.data.BarData;
|
||||
import com.github.mikephil.charting.data.BarDataSet;
|
||||
import com.github.mikephil.charting.data.BarEntry;
|
||||
import com.github.mikephil.charting.data.ChartData;
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
import com.github.mikephil.charting.data.LineData;
|
||||
import com.github.mikephil.charting.data.LineDataSet;
|
||||
@ -39,10 +40,12 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Measurement;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class LiveActivityFragment extends AbstractChartFragment {
|
||||
@ -63,6 +66,9 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
private final Steps mSteps = new Steps();
|
||||
private ScheduledExecutorService pulseScheduler;
|
||||
private int maxStepsResetCounter;
|
||||
private List<Measurement> heartRateValues;
|
||||
private LineDataSet mHeartRateSet;
|
||||
private int mHeartRate;
|
||||
|
||||
private class Steps {
|
||||
private int initialSteps;
|
||||
@ -145,16 +151,36 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case DeviceService.ACTION_REALTIME_STEPS:
|
||||
case DeviceService.ACTION_REALTIME_STEPS: {
|
||||
int steps = intent.getIntExtra(DeviceService.EXTRA_REALTIME_STEPS, 0);
|
||||
long timestamp = intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
|
||||
refreshCurrentSteps(steps, timestamp);
|
||||
addEntries(steps, timestamp);
|
||||
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);
|
||||
if (++maxStepsResetCounter > RESET_COUNT) {
|
||||
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
|
||||
LOG.info("Steps: " + steps + ", total: " + mSteps.getTotalSteps() + ", current: " + mSteps.getStepsPerMinute(false));
|
||||
|
||||
// refreshCurrentSteps();
|
||||
// addEntries();
|
||||
}
|
||||
|
||||
private void refreshCurrentSteps() {
|
||||
private void addEntries() {
|
||||
mTotalStepsChart.setSingleEntryYValue(mSteps.getTotalSteps());
|
||||
YAxis stepsPerMinuteCurrentYAxis = mStepsPerMinuteCurrentChart.getAxisLeft();
|
||||
int maxStepsPerMinute = mSteps.getMaxStepsPerMinute();
|
||||
@ -180,24 +206,36 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
int stepsPerMinute = mSteps.getStepsPerMinute(true);
|
||||
mStepsPerMinuteCurrentChart.setSingleEntryYValue(stepsPerMinute);
|
||||
|
||||
if (!addHistoryDataSet(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ChartData data = mStepsPerMinuteHistoryChart.getData();
|
||||
data.addXValue("");
|
||||
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));
|
||||
}
|
||||
|
||||
private boolean addHistoryDataSet(boolean force) {
|
||||
if (mStepsPerMinuteHistoryChart.getData() == null) {
|
||||
if (mSteps.getTotalSteps() == 0) {
|
||||
return; // ignore the first default value to keep the "no-data-description" visible
|
||||
}
|
||||
// ignore the first default value to keep the "no-data-description" visible
|
||||
if (force || mSteps.getTotalSteps() > 0) {
|
||||
LineData data = new LineData();
|
||||
mStepsPerMinuteHistoryChart.setData(data);
|
||||
data.addDataSet(mHistorySet);
|
||||
data.addDataSet(mHeartRateSet);
|
||||
mStepsPerMinuteHistoryChart.setData(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
LineData historyData = (LineData) mStepsPerMinuteHistoryChart.getData();
|
||||
historyData.addXValue("");
|
||||
historyData.addEntry(new Entry(stepsPerMinute, mHistorySet.getEntryCount()), 0);
|
||||
|
||||
mTotalStepsData.notifyDataSetChanged();
|
||||
mStepsPerMinuteData.notifyDataSetChanged();
|
||||
mStepsPerMinuteHistoryChart.notifyDataSetChanged();
|
||||
|
||||
renderCharts();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -205,6 +243,8 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
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);
|
||||
|
||||
@ -227,16 +267,12 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (pulseScheduler != null) {
|
||||
pulseScheduler.shutdownNow();
|
||||
pulseScheduler = null;
|
||||
}
|
||||
stopActivityPulse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
pulseScheduler = startActivityPulse();
|
||||
}
|
||||
|
||||
private ScheduledExecutorService startActivityPulse() {
|
||||
@ -258,11 +294,33 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
return service;
|
||||
}
|
||||
|
||||
private void stopActivityPulse() {
|
||||
if (pulseScheduler != null) {
|
||||
pulseScheduler.shutdownNow();
|
||||
pulseScheduler = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in the UI thread.
|
||||
*/
|
||||
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() {
|
||||
@ -272,15 +330,19 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
@Override
|
||||
protected void onMadeVisibleInActivity() {
|
||||
GBApplication.deviceService().onEnableRealtimeSteps(true);
|
||||
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(true);
|
||||
super.onMadeVisibleInActivity();
|
||||
if (getActivity() != null) {
|
||||
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
pulseScheduler = startActivityPulse();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMadeInvisibleInActivity() {
|
||||
stopActivityPulse();
|
||||
GBApplication.deviceService().onEnableRealtimeSteps(false);
|
||||
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(false);
|
||||
if (getActivity() != null) {
|
||||
getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
@ -289,6 +351,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
onMadeInvisibleInActivity();
|
||||
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mReceiver);
|
||||
super.onDestroyView();
|
||||
}
|
||||
@ -346,6 +409,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
private void setupHistoryChart(BarLineChartBase chart) {
|
||||
configureBarLineChartDefaults(chart);
|
||||
|
||||
chart.setTouchEnabled(false); // no zooming or anything, because it's updated all the time
|
||||
chart.setBackgroundColor(BACKGROUND_COLOR);
|
||||
chart.setDescriptionColor(DESCRIPTION_COLOR);
|
||||
chart.setDescription(getString(R.string.live_activity_steps_per_minute_history));
|
||||
@ -367,22 +431,28 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
y.setDrawGridLines(false);
|
||||
y.setDrawTopYLabelEntry(false);
|
||||
y.setTextColor(CHART_TEXT_COLOR);
|
||||
|
||||
y.setEnabled(true);
|
||||
y.setAxisMinValue(0);
|
||||
|
||||
YAxis yAxisRight = chart.getAxisRight();
|
||||
yAxisRight.setDrawGridLines(false);
|
||||
yAxisRight.setEnabled(false);
|
||||
yAxisRight.setDrawLabels(false);
|
||||
yAxisRight.setEnabled(true);
|
||||
yAxisRight.setDrawLabels(true);
|
||||
yAxisRight.setDrawTopYLabelEntry(false);
|
||||
yAxisRight.setTextColor(CHART_TEXT_COLOR);
|
||||
yAxisRight.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.setAxisDependency(YAxis.AxisDependency.LEFT);
|
||||
mHistorySet.setColor(akActivity.color);
|
||||
mHistorySet.setDrawCircles(false);
|
||||
mHistorySet.setDrawCubic(true);
|
||||
mHistorySet.setDrawFilled(true);
|
||||
mHistorySet.setDrawValues(false);
|
||||
|
||||
mHeartRateSet = createHeartrateSet(new ArrayList<Entry>(), getString(R.string.live_activity_heart_rate));
|
||||
mHeartRateSet.setDrawValues(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -402,7 +472,13 @@ public class LiveActivityFragment extends AbstractChartFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refreshInBackground(DBHandler db, GBDevice device) {
|
||||
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateChartsnUIThread(ChartsData chartsData) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -8,8 +8,8 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.github.mikephil.charting.animation.Easing;
|
||||
import com.github.mikephil.charting.charts.BarLineChartBase;
|
||||
import com.github.mikephil.charting.charts.Chart;
|
||||
import com.github.mikephil.charting.charts.CombinedChart;
|
||||
import com.github.mikephil.charting.charts.PieChart;
|
||||
import com.github.mikephil.charting.components.XAxis;
|
||||
import com.github.mikephil.charting.components.YAxis;
|
||||
@ -27,6 +27,7 @@ import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
|
||||
@ -39,7 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
public class SleepChartFragment extends AbstractChartFragment {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class);
|
||||
|
||||
private BarLineChartBase mActivityChart;
|
||||
private CombinedChart mActivityChart;
|
||||
private PieChart mSleepAmountChart;
|
||||
|
||||
private int mSmartAlarmFrom = -1;
|
||||
@ -48,14 +49,16 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
private int mSmartAlarmGoneOff = -1;
|
||||
|
||||
@Override
|
||||
protected void refreshInBackground(DBHandler db, GBDevice device) {
|
||||
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
|
||||
List<ActivitySample> samples = getSamples(db, device);
|
||||
|
||||
refresh(device, mActivityChart, samples);
|
||||
refreshSleepAmounts(device, mSleepAmountChart, samples);
|
||||
MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples);
|
||||
DefaultChartsData chartsData = refresh(device, samples);
|
||||
|
||||
return new MyChartsData(mySleepChartsData, chartsData);
|
||||
}
|
||||
|
||||
private void refreshSleepAmounts(GBDevice mGBDevice, PieChart pieChart, List<ActivitySample> samples) {
|
||||
private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<ActivitySample> samples) {
|
||||
ActivityAnalysis analysis = new ActivityAnalysis();
|
||||
ActivityAmounts amounts = analysis.calculateActivityAmounts(samples);
|
||||
PieData data = new PieData();
|
||||
@ -73,7 +76,6 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
}
|
||||
}
|
||||
String totalSleep = DateTimeUtils.formatDurationHoursMinutes(totalSeconds, TimeUnit.SECONDS);
|
||||
pieChart.setCenterText(totalSleep);
|
||||
PieDataSet set = new PieDataSet(entries, "");
|
||||
set.setValueFormatter(new ValueFormatter() {
|
||||
@Override
|
||||
@ -83,10 +85,18 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
});
|
||||
set.setColors(colors);
|
||||
data.setDataSet(set);
|
||||
pieChart.setData(data);
|
||||
|
||||
pieChart.getLegend().setEnabled(false);
|
||||
//setupLegend(pieChart);
|
||||
return new MySleepChartsData(totalSleep, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateChartsnUIThread(ChartsData chartsData) {
|
||||
MyChartsData mcd = (MyChartsData) chartsData;
|
||||
mSleepAmountChart.setCenterText(mcd.getPieData().getTotalSleep());
|
||||
mSleepAmountChart.setData(mcd.getPieData().getPieData());
|
||||
|
||||
mActivityChart.setData(mcd.getChartsData().getCombinedData());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -99,7 +109,7 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_sleepchart, container, false);
|
||||
|
||||
mActivityChart = (BarLineChartBase) rootView.findViewById(R.id.sleepchart);
|
||||
mActivityChart = (CombinedChart) rootView.findViewById(R.id.sleepchart);
|
||||
mSleepAmountChart = (PieChart) rootView.findViewById(R.id.sleepchart_pie_light_deep);
|
||||
|
||||
setupActivityChart();
|
||||
@ -132,6 +142,7 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
mSleepAmountChart.setDescription("");
|
||||
mSleepAmountChart.setNoDataTextDescription("");
|
||||
mSleepAmountChart.setNoDataText("");
|
||||
mSleepAmountChart.getLegend().setEnabled(false);
|
||||
}
|
||||
|
||||
private void setupActivityChart() {
|
||||
@ -151,6 +162,7 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
// y.setDrawLabels(false);
|
||||
// TODO: make fixed max value optional
|
||||
y.setAxisMaxValue(1f);
|
||||
y.setAxisMinValue(0);
|
||||
y.setDrawTopYLabelEntry(false);
|
||||
y.setTextColor(CHART_TEXT_COLOR);
|
||||
|
||||
@ -159,10 +171,12 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
|
||||
YAxis yAxisRight = mActivityChart.getAxisRight();
|
||||
yAxisRight.setDrawGridLines(false);
|
||||
yAxisRight.setEnabled(false);
|
||||
yAxisRight.setDrawLabels(false);
|
||||
yAxisRight.setDrawTopYLabelEntry(false);
|
||||
yAxisRight.setEnabled(supportsHeartrate());
|
||||
yAxisRight.setDrawLabels(true);
|
||||
yAxisRight.setDrawTopYLabelEntry(true);
|
||||
yAxisRight.setTextColor(CHART_TEXT_COLOR);
|
||||
yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE);
|
||||
yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE);
|
||||
}
|
||||
|
||||
protected void setupLegend(Chart chart) {
|
||||
@ -172,6 +186,10 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
legendLabels.add(akLightSleep.label);
|
||||
legendColors.add(akDeepSleep.color);
|
||||
legendLabels.add(akDeepSleep.label);
|
||||
if (supportsHeartrate()) {
|
||||
legendColors.add(HEARTRATE_COLOR);
|
||||
legendLabels.add(HEARTRATE_LABEL);
|
||||
}
|
||||
chart.getLegend().setCustom(legendColors, legendLabels);
|
||||
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
|
||||
}
|
||||
@ -187,4 +205,40 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
mActivityChart.animateX(ANIM_TIME, Easing.EasingOption.EaseInOutQuart);
|
||||
mSleepAmountChart.invalidate();
|
||||
}
|
||||
|
||||
private static class MySleepChartsData extends ChartsData {
|
||||
private String totalSleep;
|
||||
private final PieData pieData;
|
||||
|
||||
public MySleepChartsData(String totalSleep, PieData pieData) {
|
||||
this.totalSleep = totalSleep;
|
||||
this.pieData = pieData;
|
||||
}
|
||||
|
||||
public PieData getPieData() {
|
||||
return pieData;
|
||||
}
|
||||
|
||||
public CharSequence getTotalSleep() {
|
||||
return totalSleep;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyChartsData extends ChartsData {
|
||||
private final DefaultChartsData 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,8 +6,8 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.github.mikephil.charting.charts.BarLineChartBase;
|
||||
import com.github.mikephil.charting.charts.Chart;
|
||||
import com.github.mikephil.charting.charts.CombinedChart;
|
||||
import com.github.mikephil.charting.charts.PieChart;
|
||||
import com.github.mikephil.charting.components.LimitLine;
|
||||
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.BarDataSet;
|
||||
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.PieData;
|
||||
import com.github.mikephil.charting.data.PieDataSet;
|
||||
@ -41,19 +42,30 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
|
||||
private Locale mLocale;
|
||||
private int mTargetSteps = 10000;
|
||||
|
||||
private BarLineChartBase mWeekStepsChart;
|
||||
private PieChart mTodayStepsChart;
|
||||
private CombinedChart mWeekStepsChart;
|
||||
|
||||
@Override
|
||||
protected void refreshInBackground(DBHandler db, GBDevice device) {
|
||||
ChartsHost chartsHost = getChartsHost();
|
||||
if (chartsHost != null) {
|
||||
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
|
||||
Calendar day = Calendar.getInstance();
|
||||
day.setTime(chartsHost.getEndDate());
|
||||
//NB: we could have omitted the day, but this way we can move things to the past easily
|
||||
refreshDaySteps(db, mTodayStepsChart, day, device);
|
||||
refreshWeekBeforeSteps(db, mWeekStepsChart, day, device);
|
||||
DaySteps daySteps = refreshDaySteps(db, day, device);
|
||||
DefaultChartsData weekBeforeStepsData = 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
|
||||
@ -62,7 +74,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
|
||||
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();
|
||||
|
||||
@ -80,24 +92,25 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
|
||||
BarDataSet set = new BarDataSet(entries, "");
|
||||
set.setColor(akActivity.color);
|
||||
|
||||
BarData data = new BarData(labels, set);
|
||||
data.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false);
|
||||
BarData barData = new BarData(labels, set);
|
||||
barData.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false);
|
||||
|
||||
LimitLine target = new LimitLine(mTargetSteps);
|
||||
barChart.getAxisLeft().removeAllLimitLines();
|
||||
barChart.getAxisLeft().addLimitLine(target);
|
||||
combinedChart.getAxisLeft().removeAllLimitLines();
|
||||
combinedChart.getAxisLeft().addLimitLine(target);
|
||||
|
||||
setupLegend(barChart);
|
||||
barChart.setData(data);
|
||||
barChart.getLegend().setEnabled(false);
|
||||
CombinedData combinedData = new CombinedData(labels);
|
||||
combinedData.setData(barData);
|
||||
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();
|
||||
|
||||
int totalSteps = analysis.calculateTotalSteps(getSamplesOfDay(db, day, device));
|
||||
|
||||
pieChart.setCenterText(NumberFormat.getNumberInstance(mLocale).format(totalSteps));
|
||||
PieData data = new PieData();
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
List<Integer> colors = new ArrayList<>();
|
||||
@ -119,9 +132,8 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
|
||||
data.setDataSet(set);
|
||||
//this hides the values (numeric) added to the set. These would be shown aside the strings set with addXValue above
|
||||
data.setDrawValues(false);
|
||||
pieChart.setData(data);
|
||||
|
||||
pieChart.getLegend().setEnabled(false);
|
||||
return new DaySteps(data, totalSteps);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -137,7 +149,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
|
||||
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);
|
||||
|
||||
setupWeekStepsChart();
|
||||
@ -160,6 +172,8 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
|
||||
mTodayStepsChart.setDescription(getContext().getString(R.string.weeksteps_today_steps_description, mTargetSteps));
|
||||
mTodayStepsChart.setNoDataTextDescription("");
|
||||
mTodayStepsChart.setNoDataText("");
|
||||
mTodayStepsChart.getLegend().setEnabled(false);
|
||||
// setupLegend(mTodayStepsChart);
|
||||
}
|
||||
|
||||
private void setupWeekStepsChart() {
|
||||
@ -192,12 +206,12 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
|
||||
}
|
||||
|
||||
protected void setupLegend(Chart chart) {
|
||||
List<Integer> legendColors = new ArrayList<>(1);
|
||||
List<String> legendLabels = new ArrayList<>(1);
|
||||
legendColors.add(akActivity.color);
|
||||
legendLabels.add(getContext().getString(R.string.chart_steps));
|
||||
chart.getLegend().setCustom(legendColors, legendLabels);
|
||||
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
|
||||
// List<Integer> legendColors = new ArrayList<>(1);
|
||||
// List<String> legendLabels = new ArrayList<>(1);
|
||||
// legendColors.add(akActivity.color);
|
||||
// legendLabels.add(getContext().getString(R.string.chart_steps));
|
||||
// chart.getLegend().setCustom(legendColors, legendLabels);
|
||||
// chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
|
||||
}
|
||||
|
||||
private List<ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, GBDevice device) {
|
||||
@ -222,4 +236,32 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
|
||||
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
|
||||
@ -152,7 +153,7 @@ public class GBAlarmListAdapter extends ArrayAdapter<GBAlarm> {
|
||||
if (isOn) {
|
||||
view.setTextColor(Color.BLUE);
|
||||
} else {
|
||||
view.setTextColor(Color.BLACK);
|
||||
view.setTextColor(GBApplication.getTextColor(mContext));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,14 +8,17 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
|
||||
|
||||
/**
|
||||
* Adapter for displaying GBDevice instances.
|
||||
@ -32,7 +35,7 @@ public class GBDeviceAdapter extends ArrayAdapter<GBDevice> {
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
GBDevice device = getItem(position);
|
||||
final GBDevice device = getItem(position);
|
||||
|
||||
if (view == null) {
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
@ -42,33 +45,60 @@ public class GBDeviceAdapter extends ArrayAdapter<GBDevice> {
|
||||
}
|
||||
TextView deviceStatusLabel = (TextView) view.findViewById(R.id.device_status);
|
||||
TextView deviceNameLabel = (TextView) view.findViewById(R.id.device_name);
|
||||
TextView deviceInfoLabel = (TextView) view.findViewById(R.id.device_info);
|
||||
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);
|
||||
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);
|
||||
|
||||
deviceNameLabel.setText(getUniqueDeviceName(device));
|
||||
deviceInfoLabel.setText(device.getInfoString());
|
||||
|
||||
if (device.isBusy()) {
|
||||
deviceStatusLabel.setText(device.getBusyTask());
|
||||
busyIndicator.setVisibility(View.VISIBLE);
|
||||
batteryStatusLabel.setVisibility(View.GONE);
|
||||
deviceInfoLabel.setVisibility(View.GONE);
|
||||
batteryLabel.setVisibility(View.INVISIBLE);
|
||||
batteryStatusLabel.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
deviceStatusLabel.setText(device.getStateString());
|
||||
busyIndicator.setVisibility(View.GONE);
|
||||
busyIndicator.setVisibility(View.INVISIBLE);
|
||||
batteryLabel.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();
|
||||
if (batteryLevel != GBDevice.BATTERY_UNKNOWN) {
|
||||
batteryStatusLabel.setText("BAT: " + device.getBatteryLevel() + "%");
|
||||
batteryLabel.setText("BAT:");
|
||||
batteryStatusLabel.setText(device.getBatteryLevel() + "%");
|
||||
BatteryState batteryState = device.getBatteryState();
|
||||
if (BatteryState.BATTERY_LOW.equals(batteryState)) {
|
||||
batteryLabel.setTextColor(Color.RED);
|
||||
batteryStatusLabel.setTextColor(Color.RED);
|
||||
} else {
|
||||
batteryLabel.setTextColor(ContextCompat.getColor(getContext(), R.color.secondarytext));
|
||||
batteryStatusLabel.setTextColor(ContextCompat.getColor(getContext(), R.color.secondarytext));
|
||||
|
||||
if (BatteryState.BATTERY_CHARGING.equals(batteryState) ||
|
||||
@ -77,28 +107,68 @@ public class GBDeviceAdapter extends ArrayAdapter<GBDevice> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
batteryLabel.setText("");
|
||||
batteryStatusLabel.setText("");
|
||||
}
|
||||
|
||||
switch (device.getType()) {
|
||||
case PEBBLE:
|
||||
if (device.isConnected()) {
|
||||
deviceImageView.setImageResource(R.drawable.ic_device_pebble);
|
||||
} else {
|
||||
deviceImageView.setImageResource(R.drawable.ic_device_pebble_disabled);
|
||||
}
|
||||
break;
|
||||
case MIBAND:
|
||||
if (device.isConnected()) {
|
||||
deviceImageView.setImageResource(R.drawable.ic_device_miband);
|
||||
} else {
|
||||
deviceImageView.setImageResource(R.drawable.ic_device_miband_disabled);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (device.isConnected()) {
|
||||
deviceImageView.setImageResource(R.drawable.ic_launcher);
|
||||
} else {
|
||||
deviceImageView.setImageResource(R.drawable.ic_device_default_disabled);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
String deviceName = device.getName();
|
||||
if (!isUniqueDeviceName(device, deviceName)) {
|
||||
if (device.getHardwareVersion() != null) {
|
||||
deviceName = deviceName + " " + device.getHardwareVersion();
|
||||
if (!isUniqueDeviceName(device, deviceName)) {
|
||||
deviceName = deviceName + " " + device.getShortAddress();
|
||||
}
|
||||
} else {
|
||||
deviceName = deviceName + " " + device.getShortAddress();
|
||||
}
|
||||
}
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,12 @@ import nodomain.freeyourgadget.gadgetbridge.model.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 boolean horizontalAlignment;
|
||||
private int size = SIZE_MEDIUM;
|
||||
|
||||
public ItemWithDetailsAdapter(Context context, List<ItemWithDetails> items) {
|
||||
super(context, 0, items);
|
||||
@ -26,6 +31,10 @@ public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void setHorizontalAlignment(boolean horizontalAlignment) {
|
||||
this.horizontalAlignment = horizontalAlignment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
ItemWithDetails item = getItem(position);
|
||||
@ -34,7 +43,18 @@ public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
if (horizontalAlignment) {
|
||||
view = inflater.inflate(R.layout.item_with_details_horizontal, parent, false);
|
||||
} else {
|
||||
switch (size) {
|
||||
case SIZE_SMALL:
|
||||
view = inflater.inflate(R.layout.item_with_details_small, parent, false);
|
||||
break;
|
||||
default:
|
||||
view = inflater.inflate(R.layout.item_with_details, parent, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImageView iconView = (ImageView) view.findViewById(R.id.item_image);
|
||||
TextView nameView = (TextView) view.findViewById(R.id.item_name);
|
||||
@ -46,4 +66,12 @@ public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public void setSize(int size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
@ -6,16 +6,16 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class PebbleContentProvider extends ContentProvider {
|
||||
|
||||
@ -59,8 +59,8 @@ public class PebbleContentProvider extends ContentProvider {
|
||||
MatrixCursor mc = new MatrixCursor(columnNames);
|
||||
int connected = 0;
|
||||
int appMessage = 0;
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this.getContext());
|
||||
if (sharedPrefs.getBoolean("pebble_enable_pebblekit", false)) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (prefs.getBoolean("pebble_enable_pebblekit", false)) {
|
||||
appMessage = 1;
|
||||
}
|
||||
if (mGBDevice != null && mGBDevice.getType() == DeviceType.PEBBLE && mGBDevice.isInitialized()) {
|
||||
|
@ -22,6 +22,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.DATABASE_NAME;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_CUSTOM_SHORT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_INTENSITY;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_PROVIDER;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS;
|
||||
@ -33,7 +34,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
|
||||
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) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
@ -51,6 +52,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
LOG.info("ActivityDatabase: schema upgrade requested from " + oldVersion + " to " + newVersion);
|
||||
try {
|
||||
for (int i = oldVersion + 1; i <= newVersion; i++) {
|
||||
DBUpdateScript updater = getUpdateScript(db, i);
|
||||
@ -68,6 +70,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
LOG.info("ActivityDatabase: schema downgrade requested from " + oldVersion + " to " + newVersion);
|
||||
try {
|
||||
for (int i = oldVersion; i >= newVersion; i--) {
|
||||
DBUpdateScript updater = getUpdateScript(db, i);
|
||||
@ -101,6 +104,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
values.put(KEY_PROVIDER, sample.getProvider().getID());
|
||||
values.put(KEY_INTENSITY, sample.getRawIntensity());
|
||||
values.put(KEY_STEPS, sample.getSteps());
|
||||
values.put(KEY_CUSTOM_SHORT, sample.getCustomValue());
|
||||
values.put(KEY_TYPE, sample.getRawKind());
|
||||
|
||||
db.insert(TABLE_GBACTIVITYSAMPLES, null, values);
|
||||
@ -115,9 +119,10 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
* @param intensity the sample's raw intensity value
|
||||
* @param steps the sample's steps value
|
||||
* @param kind the raw activity kind of the sample
|
||||
* @param customShortValue
|
||||
*/
|
||||
@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) {
|
||||
LOG.error("negative intensity received, ignoring");
|
||||
intensity = 0;
|
||||
@ -127,6 +132,11 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
steps = 0;
|
||||
}
|
||||
|
||||
if (customShortValue < 0) {
|
||||
LOG.error("negative short value received, ignoring");
|
||||
customShortValue = 0;
|
||||
}
|
||||
|
||||
try (SQLiteDatabase db = this.getWritableDatabase()) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KEY_TIMESTAMP, timestamp);
|
||||
@ -134,6 +144,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
values.put(KEY_INTENSITY, intensity);
|
||||
values.put(KEY_STEPS, steps);
|
||||
values.put(KEY_TYPE, kind);
|
||||
values.put(KEY_CUSTOM_SHORT, customShortValue);
|
||||
|
||||
db.insert(TABLE_GBACTIVITYSAMPLES, null, values);
|
||||
}
|
||||
@ -144,8 +155,8 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
try (SQLiteDatabase db = this.getWritableDatabase()) {
|
||||
|
||||
String sql = "INSERT INTO " + TABLE_GBACTIVITYSAMPLES + " (" + KEY_TIMESTAMP + "," +
|
||||
KEY_PROVIDER + "," + KEY_INTENSITY + "," + KEY_STEPS + "," + KEY_TYPE + ")" +
|
||||
" VALUES (?,?,?,?,?);";
|
||||
KEY_PROVIDER + "," + KEY_INTENSITY + "," + KEY_STEPS + "," + KEY_TYPE + "," + KEY_CUSTOM_SHORT + ")" +
|
||||
" VALUES (?,?,?,?,?,?);";
|
||||
SQLiteStatement statement = db.compileStatement(sql);
|
||||
db.beginTransaction();
|
||||
|
||||
@ -156,6 +167,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
statement.bindLong(3, activitySample.getRawIntensity());
|
||||
statement.bindLong(4, activitySample.getSteps());
|
||||
statement.bindLong(5, activitySample.getRawKind());
|
||||
statement.bindLong(6, activitySample.getCustomValue());
|
||||
statement.execute();
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
@ -209,16 +221,20 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
try (SQLiteDatabase db = this.getReadableDatabase()) {
|
||||
try (Cursor cursor = db.query(TABLE_GBACTIVITYSAMPLES, null, where, null, null, null, order)) {
|
||||
LOG.info("Activity query result: " + cursor.getCount() + " samples");
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
int colTimeStamp = cursor.getColumnIndex(KEY_TIMESTAMP);
|
||||
int colIntensity = cursor.getColumnIndex(KEY_INTENSITY);
|
||||
int colSteps = cursor.getColumnIndex(KEY_STEPS);
|
||||
int colType = cursor.getColumnIndex(KEY_TYPE);
|
||||
int colCustomShort = cursor.getColumnIndex(KEY_CUSTOM_SHORT);
|
||||
while (cursor.moveToNext()) {
|
||||
GBActivitySample sample = new GBActivitySample(
|
||||
provider,
|
||||
cursor.getInt(cursor.getColumnIndex(KEY_TIMESTAMP)),
|
||||
cursor.getShort(cursor.getColumnIndex(KEY_INTENSITY)),
|
||||
cursor.getShort(cursor.getColumnIndex(KEY_STEPS)),
|
||||
(byte) cursor.getShort(cursor.getColumnIndex(KEY_TYPE)));
|
||||
cursor.getInt(colTimeStamp),
|
||||
cursor.getInt(colIntensity),
|
||||
cursor.getInt(colSteps),
|
||||
cursor.getInt(colType),
|
||||
cursor.getInt(colCustomShort));
|
||||
samples.add(sample);
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -232,7 +248,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
}
|
||||
|
||||
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++) {
|
||||
builder.append(" type=").append(dbActivityTypes[i]);
|
||||
if (i + 1 < dbActivityTypes.length) {
|
||||
@ -242,4 +258,50 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
builder.append(')');
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -10,5 +10,6 @@ public class DBConstants {
|
||||
public static final String KEY_PROVIDER = "provider";
|
||||
public static final String KEY_INTENSITY = "intensity";
|
||||
public static final String KEY_STEPS = "steps";
|
||||
public static final String KEY_CUSTOM_SHORT = "customShort";
|
||||
public static final String KEY_TYPE = "type";
|
||||
}
|
||||
|
@ -24,9 +24,16 @@ public interface DBHandler {
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
@ -70,6 +71,22 @@ public class DBHelper {
|
||||
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
|
||||
* with Lollipop and later.
|
||||
|
@ -4,6 +4,7 @@ import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
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_PROVIDER;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS;
|
||||
@ -19,6 +20,7 @@ public class ActivityDBCreationScript {
|
||||
+ KEY_INTENSITY + " SMALLINT,"
|
||||
+ KEY_STEPS + " TINYINT,"
|
||||
+ KEY_TYPE + " TINYINT,"
|
||||
+ KEY_CUSTOM_SHORT + " INT,"
|
||||
+ " PRIMARY KEY (" + KEY_TIMESTAMP + "," + KEY_PROVIDER + ") ON CONFLICT REPLACE)" + DBHelper.getWithoutRowId();
|
||||
db.execSQL(CREATE_GBACTIVITYSAMPLES_TABLE);
|
||||
}
|
||||
|
@ -5,27 +5,23 @@ 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;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_CUSTOM_SHORT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES;
|
||||
|
||||
/**
|
||||
* Adds a table "STEPS_PER_DAY".
|
||||
* Adds a column "customShort" to the table "GBActivitySamples"
|
||||
*/
|
||||
public class ActivityDBUpdate_6 implements DBUpdateScript {
|
||||
@Override
|
||||
public void upgradeSchema(SQLiteDatabase db) {
|
||||
String CREATE_STEPS_PER_DAY_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_STEPS_PER_DAY + " ("
|
||||
+ KEY_TIMESTAMP + " INT,"
|
||||
+ KEY_PROVIDER + " TINYINT,"
|
||||
+ KEY_STEPS + " MEDIUMINT,"
|
||||
+ " PRIMARY KEY (" + KEY_TIMESTAMP + "," + KEY_PROVIDER + ") ON CONFLICT REPLACE)" + DBHelper.getWithoutRowId();
|
||||
db.execSQL(CREATE_STEPS_PER_DAY_TABLE);
|
||||
if (!DBHelper.existsColumn(TABLE_GBACTIVITYSAMPLES, KEY_CUSTOM_SHORT, db)) {
|
||||
String ADD_COLUMN_CUSTOM_SHORT = "ALTER TABLE " + TABLE_GBACTIVITYSAMPLES + " ADD COLUMN "
|
||||
+ KEY_CUSTOM_SHORT + " INT;";
|
||||
db.execSQL(ADD_COLUMN_CUSTOM_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downgradeSchema(SQLiteDatabase db) {
|
||||
DBHelper.dropTable(TABLE_STEPS_PER_DAY, db);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
* This interface is implemented at least once for every supported gadget device.
|
||||
* It allows Gadgetbridge to generically deal with different kinds of devices
|
||||
* without actually knowing the details of any device.
|
||||
*
|
||||
* <p/>
|
||||
* 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
|
||||
* the given device.
|
||||
@ -22,6 +22,7 @@ public interface DeviceCoordinator {
|
||||
|
||||
/**
|
||||
* Checks whether this candidate handles the given candidate.
|
||||
*
|
||||
* @param 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.
|
||||
*
|
||||
* @param 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.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
DeviceType getDeviceType();
|
||||
@ -43,6 +46,7 @@ public interface DeviceCoordinator {
|
||||
/**
|
||||
* Returns the Activity class to be started in order to perform a pairing of a
|
||||
* given device.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<? extends Activity> getPairingActivity();
|
||||
@ -50,6 +54,7 @@ public interface DeviceCoordinator {
|
||||
/**
|
||||
* Returns the Activity class that will be used as the primary activity
|
||||
* for the given device.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<? extends Activity> getPrimaryActivity();
|
||||
@ -57,6 +62,7 @@ public interface DeviceCoordinator {
|
||||
/**
|
||||
* Returns true if activity data fetching is supported by the device
|
||||
* (with this coordinator).
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean supportsActivityDataFetching();
|
||||
@ -65,6 +71,7 @@ public interface DeviceCoordinator {
|
||||
* Returns true if activity data fetching is supported AND possible at this
|
||||
* very moment. This will consider the device state (being connected/disconnected/busy...)
|
||||
* etc.
|
||||
*
|
||||
* @param device
|
||||
* @return
|
||||
*/
|
||||
@ -72,6 +79,7 @@ public interface DeviceCoordinator {
|
||||
|
||||
/**
|
||||
* Returns the sample provider for the device being supported.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
SampleProvider getSampleProvider();
|
||||
@ -79,6 +87,7 @@ public interface DeviceCoordinator {
|
||||
/**
|
||||
* Finds an install handler for the given uri that can install the given
|
||||
* uri on the device being managed.
|
||||
*
|
||||
* @param uri
|
||||
* @param context
|
||||
* @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.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean supportsScreenshots();
|
||||
|
||||
/**
|
||||
* Returns true if this device/coordinator supports settig alarms.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean supportsAlarmConfiguration();
|
||||
|
||||
int getTapString();
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
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.ServiceCommand;
|
||||
|
||||
/**
|
||||
* 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 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);
|
||||
|
||||
@ -36,11 +36,19 @@ public interface EventHandler {
|
||||
|
||||
void onAppDelete(UUID uuid);
|
||||
|
||||
void onAppConfiguration(UUID appUuid, String config);
|
||||
|
||||
void onFetchActivityData();
|
||||
|
||||
void onReboot();
|
||||
|
||||
void onHeartRateTest();
|
||||
|
||||
void onEnableRealtimeHeartRateMeasurement(boolean enable);
|
||||
|
||||
void onFindDevice(boolean start);
|
||||
|
||||
void onScreenshotReq();
|
||||
|
||||
void onEnableHeartRateSleepSupport(boolean enable);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ public interface InstallHandler {
|
||||
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);
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices;
|
||||
|
||||
public interface SampleProvider {
|
||||
byte PROVIDER_MIBAND = 0;
|
||||
byte PROVIDER_PEBBLE_MORPHEUZ = 1;
|
||||
byte PROVIDER_PEBBLE_GADGETBRIDGE = 2;
|
||||
byte PROVIDER_PEBBLE_MISFIT = 3;
|
||||
int PROVIDER_MIBAND = 0;
|
||||
int PROVIDER_PEBBLE_MORPHEUZ = 1;
|
||||
int PROVIDER_PEBBLE_GADGETBRIDGE = 2;
|
||||
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();
|
||||
}
|
||||
|
@ -15,22 +15,22 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
private static final class UnknownSampleProvider implements SampleProvider {
|
||||
@Override
|
||||
public int normalizeType(byte rawType) {
|
||||
public int normalizeType(int rawType) {
|
||||
return ActivityKind.TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte toRawActivityKind(int activityKind) {
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(short rawIntensity) {
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getID() {
|
||||
public int getID() {
|
||||
return PROVIDER_UNKNOWN;
|
||||
}
|
||||
}
|
||||
@ -83,4 +83,14 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
public boolean supportsScreenshots() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAlarmConfiguration() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,21 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.miband;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public final class MiBandConst {
|
||||
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_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_ADDRESS = "development_miaddr"; // FIXME: should be prefixed mi_
|
||||
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_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_USE_HR_FOR_SLEEP_DETECTION = "mi_hr_sleep_detection";
|
||||
|
||||
|
||||
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_1A = "1A";
|
||||
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 value = null;
|
||||
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;
|
||||
}
|
||||
return prefs.getInt(key, 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);
|
||||
return prefs.getString(key, defaultValue);
|
||||
}
|
||||
|
@ -2,23 +2,22 @@ package nodomain.freeyourgadget.gadgetbridge.devices.miband;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MiBandCoordinator.class);
|
||||
@ -75,6 +74,16 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAlarmConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
return R.string.tap_connected_device_for_activity;
|
||||
}
|
||||
|
||||
public static boolean hasValidUserInfo() {
|
||||
String dummyMacAddress = MiBandService.MAC_ADDRESS_FILTER_1_1A + ":00:00:00";
|
||||
try {
|
||||
@ -107,22 +116,16 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
* @throws IllegalArgumentException when the user info can not be created
|
||||
*/
|
||||
public static UserInfo getConfiguredUserInfo(String miBandAddress) throws IllegalArgumentException {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext());
|
||||
int userYear = Integer.parseInt(prefs.getString(MiBandConst.PREF_USER_YEAR_OF_BIRTH, "0"));
|
||||
int age = 25;
|
||||
if (userYear > 1900) {
|
||||
age = Calendar.getInstance().get(Calendar.YEAR) - userYear;
|
||||
if (age <= 0) {
|
||||
age = 25;
|
||||
}
|
||||
}
|
||||
ActivityUser activityUser = new ActivityUser();
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
|
||||
UserInfo info = UserInfo.create(
|
||||
miBandAddress,
|
||||
prefs.getString(MiBandConst.PREF_USER_ALIAS, null),
|
||||
("male".equals(prefs.getString(MiBandConst.PREF_USER_GENDER, null)) ? 1 : 0),
|
||||
age,
|
||||
Integer.parseInt(prefs.getString(MiBandConst.PREF_USER_HEIGHT_CM, "175")),
|
||||
Integer.parseInt(prefs.getString(MiBandConst.PREF_USER_WEIGHT_KG, "70")),
|
||||
activityUser.getActivityUserGender(),
|
||||
activityUser.getActivityUserAge(),
|
||||
activityUser.getActivityUserHeightCm(),
|
||||
activityUser.getActivityUserWeightKg(),
|
||||
0
|
||||
);
|
||||
return info;
|
||||
@ -130,20 +133,25 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
public static int getWearLocation(String miBandAddress) throws IllegalArgumentException {
|
||||
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"))) {
|
||||
location = 1; // right hand
|
||||
}
|
||||
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 {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext());
|
||||
return Integer.parseInt(prefs.getString(MiBandConst.PREF_MIBAND_FITNESS_GOAL, "10000"));
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getInt(MiBandConst.PREF_MIBAND_FITNESS_GOAL, 10000);
|
||||
}
|
||||
|
||||
public static int getReservedAlarmSlots(String miBandAddress) throws IllegalArgumentException {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext());
|
||||
return Integer.parseInt(prefs.getString(MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, "0"));
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getInt(MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, 0);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.miband;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -10,22 +10,28 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
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.service.devices.miband.AbstractMiFirmwareInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
|
||||
/**
|
||||
* Also see Mi1SFirmwareInfo.
|
||||
*/
|
||||
public class MiBandFWHelper {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MiBandFWHelper.class);
|
||||
|
||||
private final Uri uri;
|
||||
private final ContentResolver cr;
|
||||
private byte[] fw;
|
||||
|
||||
private final int offsetFirmwareVersionBuild = 1056;
|
||||
private final int offsetFirmwareVersionRevision = 1057;
|
||||
private final int offsetFirmwareVersionMinor = 1058;
|
||||
private final int offsetFirmwareVersionMajor = 1059;
|
||||
/**
|
||||
* The backing firmware info instance, which in general supports the provided
|
||||
* given firmware. You must call AbstractMiFirmwareInfo#checkValid() before
|
||||
* attempting to flash it.
|
||||
*/
|
||||
@NonNull
|
||||
private final AbstractMiFirmwareInfo firmwareInfo;
|
||||
@NonNull
|
||||
private final byte[] fw;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
this.uri = uri;
|
||||
cr = context.getContentResolver();
|
||||
if (cr == null) {
|
||||
throw new IOException("No content resolver");
|
||||
}
|
||||
|
||||
String pebblePattern = ".*\\.(pbw|pbz|pbl)";
|
||||
|
||||
if (uri.getPath().matches(pebblePattern)) {
|
||||
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
|
||||
if (fw.length <= offsetFirmwareVersionMajor) {
|
||||
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);
|
||||
}
|
||||
this.firmwareInfo = determineFirmwareInfoFor(fw);
|
||||
} catch (IOException ex) {
|
||||
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) {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
return fw;
|
||||
}
|
||||
@ -107,13 +115,31 @@ public class MiBandFWHelper {
|
||||
}
|
||||
|
||||
public boolean isFirmwareGenerallyCompatibleWith(GBDevice device) {
|
||||
String deviceHW = device.getHardwareVersion();
|
||||
if (MiBandConst.MI_1.equals(deviceHW)) {
|
||||
return getFirmwareVersionMajor() == 1;
|
||||
return firmwareInfo.isGenerallyCompatibleWith(device);
|
||||
}
|
||||
if (MiBandConst.MI_1A.equals(deviceHW)) {
|
||||
return getFirmwareVersionMajor() == 5;
|
||||
|
||||
public boolean isSingleFirmware() {
|
||||
return firmwareInfo.isSingleMiBandFirmware();
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,14 @@ public class MiBandFWInstallHandler implements InstallHandler {
|
||||
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()));
|
||||
fwItem.setIcon(R.drawable.ic_device_miband);
|
||||
|
||||
@ -56,7 +64,13 @@ public class MiBandFWInstallHandler implements InstallHandler {
|
||||
installActivity.setInstallEnabled(false);
|
||||
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()) {
|
||||
builder.append(" ").append(mContext.getString(R.string.miband_firmware_known));
|
||||
|
@ -7,11 +7,9 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
@ -26,6 +24,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.DiscoveryActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class MiBandPairingActivity extends Activity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MiBandPairingActivity.class);
|
||||
@ -159,13 +158,19 @@ public class MiBandPairingActivity extends Activity {
|
||||
}
|
||||
|
||||
private void pairingFinished(boolean pairedSuccessfully) {
|
||||
LOG.debug("pairingFinished: " + pairedSuccessfully);
|
||||
if (!isPairing) {
|
||||
// already gone?
|
||||
return;
|
||||
}
|
||||
|
||||
isPairing = false;
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mPairingReceiver);
|
||||
unregisterReceiver(mBondingReceiver);
|
||||
|
||||
if (pairedSuccessfully) {
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
sharedPrefs.edit().putString(MiBandConst.PREF_MIBAND_ADDRESS, macAddress).apply();
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
prefs.getPreferences().edit().putString(MiBandConst.PREF_MIBAND_ADDRESS, macAddress).apply();
|
||||
}
|
||||
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
||||
GB.toast(this, "Creating bond with" + bondingMacAddress, Toast.LENGTH_LONG, GB.INFO);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivity;
|
||||
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_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_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_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_PROFILE;
|
||||
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
|
||||
protected String[] getPreferenceKeysWithSummary() {
|
||||
return new String[]{
|
||||
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_FITNESS_GOAL,
|
||||
PREF_MIBAND_DONT_ACK_TRANSFER,
|
||||
PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR,
|
||||
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_SMS),
|
||||
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_SMS),
|
||||
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_INCOMING_CALL),
|
||||
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_INCOMING_CALL),
|
||||
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_K9MAIL),
|
||||
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_K9MAIL),
|
||||
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_PEBBLEMSG),
|
||||
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_PEBBLEMSG),
|
||||
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_GENERIC),
|
||||
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_GENERIC),
|
||||
};
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
public class MiBandSampleProvider implements SampleProvider {
|
||||
public static final byte TYPE_DEEP_SLEEP = 5;
|
||||
public static final byte TYPE_LIGHT_SLEEP = 4;
|
||||
public static final byte TYPE_ACTIVITY = -1;
|
||||
public static final byte TYPE_UNKNOWN = -1;
|
||||
public static final byte TYPE_NONWEAR = 3;
|
||||
public static final byte TYPE_CHARGING = 6;
|
||||
public static final int TYPE_DEEP_SLEEP = 5;
|
||||
public static final int TYPE_LIGHT_SLEEP = 4;
|
||||
public static final int TYPE_ACTIVITY = -1;
|
||||
public static final int TYPE_UNKNOWN = -1;
|
||||
public static final int TYPE_NONWEAR = 3;
|
||||
public static final int TYPE_CHARGING = 6;
|
||||
|
||||
// public static final byte TYPE_NREM = 5; // DEEP SLEEP
|
||||
// public static final byte TYPE_ONBED = 7;
|
||||
@ -23,7 +23,7 @@ public class MiBandSampleProvider implements SampleProvider {
|
||||
private final float movementDivisor = 180.0f; //256.0f;
|
||||
|
||||
@Override
|
||||
public int normalizeType(byte rawType) {
|
||||
public int normalizeType(int rawType) {
|
||||
switch (rawType) {
|
||||
case TYPE_DEEP_SLEEP:
|
||||
return ActivityKind.TYPE_DEEP_SLEEP;
|
||||
@ -42,7 +42,7 @@ public class MiBandSampleProvider implements SampleProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte toRawActivityKind(int activityKind) {
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
switch (activityKind) {
|
||||
case ActivityKind.TYPE_ACTIVITY:
|
||||
return TYPE_ACTIVITY;
|
||||
@ -59,12 +59,12 @@ public class MiBandSampleProvider implements SampleProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(short rawIntensity) {
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
return rawIntensity / movementDivisor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getID() {
|
||||
public int getID() {
|
||||
return SampleProvider.PROVIDER_MIBAND;
|
||||
}
|
||||
}
|
||||
|
@ -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_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_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_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 */
|
||||
|
||||
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;
|
||||
|
||||
// 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
|
||||
|
||||
|
||||
@ -213,6 +226,7 @@ public class MiBandService {
|
||||
static {
|
||||
MIBAND_DEBUG = new HashMap<>();
|
||||
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_NAME, "Device Name");
|
||||
@ -229,6 +243,8 @@ public class MiBandService {
|
||||
MIBAND_DEBUG.put(UUID_CHARACTERISTIC_TEST, "Test");
|
||||
MIBAND_DEBUG.put(UUID_CHARACTERISTIC_SENSOR_DATA, "Sensor Data");
|
||||
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) {
|
||||
|
@ -1,10 +1,11 @@
|
||||
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.util.CheckSums;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class UserInfo {
|
||||
|
||||
private final String btAddress;
|
||||
@ -23,7 +24,7 @@ public class UserInfo {
|
||||
* @param btAddress the address of the MI Band to connect to.
|
||||
*/
|
||||
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,7 +85,7 @@ public class UserInfo {
|
||||
sequence[8] = (byte) (type & 0xff);
|
||||
|
||||
int aliasFrom = 9;
|
||||
if (mDeviceInfo.isMili1A() || mDeviceInfo.isMilli1S()) {
|
||||
if (!mDeviceInfo.isMili1()) {
|
||||
sequence[9] = (byte) (mDeviceInfo.feature & 255);
|
||||
sequence[10] = (byte) (mDeviceInfo.appearance & 255);
|
||||
aliasFrom = 11;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -7,24 +7,24 @@ public class MisfitSampleProvider implements SampleProvider {
|
||||
protected final float movementDivisor = 300f;
|
||||
|
||||
@Override
|
||||
public int normalizeType(byte rawType) {
|
||||
public int normalizeType(int rawType) {
|
||||
return (int) rawType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte toRawActivityKind(int activityKind) {
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
return (byte) activityKind;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(short rawIntensity) {
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
return rawIntensity / movementDivisor;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte getID() {
|
||||
public int getID() {
|
||||
return SampleProvider.PROVIDER_PEBBLE_MISFIT;
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,15 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
public class MorpheuzSampleProvider implements SampleProvider {
|
||||
// raw types
|
||||
public static final byte TYPE_DEEP_SLEEP = 5;
|
||||
public static final byte TYPE_LIGHT_SLEEP = 4;
|
||||
public static final byte TYPE_ACTIVITY = -1;
|
||||
public static final byte TYPE_UNKNOWN = -1;
|
||||
public static final int TYPE_DEEP_SLEEP = 5;
|
||||
public static final int TYPE_LIGHT_SLEEP = 4;
|
||||
public static final int TYPE_ACTIVITY = -1;
|
||||
public static final int TYPE_UNKNOWN = -1;
|
||||
|
||||
protected float movementDivisor = 5000f;
|
||||
|
||||
@Override
|
||||
public int normalizeType(byte rawType) {
|
||||
public int normalizeType(int rawType) {
|
||||
switch (rawType) {
|
||||
case TYPE_DEEP_SLEEP:
|
||||
return ActivityKind.TYPE_DEEP_SLEEP;
|
||||
@ -28,7 +28,7 @@ public class MorpheuzSampleProvider implements SampleProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte toRawActivityKind(int activityKind) {
|
||||
public int toRawActivityKind(int activityKind) {
|
||||
switch (activityKind) {
|
||||
case ActivityKind.TYPE_ACTIVITY:
|
||||
return TYPE_ACTIVITY;
|
||||
@ -43,12 +43,12 @@ public class MorpheuzSampleProvider implements SampleProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(short rawIntensity) {
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
return rawIntensity / movementDivisor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getID() {
|
||||
public int getID() {
|
||||
return SampleProvider.PROVIDER_PEBBLE_MORPHEUZ;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -11,6 +13,7 @@ import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Writer;
|
||||
|
||||
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.GenericItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
|
||||
|
||||
public class PBWInstallHandler implements InstallHandler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PBWInstallHandler.class);
|
||||
@ -47,15 +51,7 @@ public class PBWInstallHandler implements InstallHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
String hwRev = device.getHardwareVersion();
|
||||
String platformName;
|
||||
if (hwRev.startsWith("snowy")) {
|
||||
platformName = "basalt";
|
||||
} else if (hwRev.startsWith("spalding")) {
|
||||
platformName = "chalk";
|
||||
} else {
|
||||
platformName = "aplite";
|
||||
}
|
||||
String platformName = PebbleUtils.getPlatformName(device.getHardwareVersion());
|
||||
|
||||
try {
|
||||
mPBWReader = new PBWReader(mUri, mContext, platformName);
|
||||
@ -158,10 +154,29 @@ public class PBWInstallHandler implements InstallHandler {
|
||||
}
|
||||
try {
|
||||
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();
|
||||
} catch (IOException 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,6 +58,8 @@ public class PBWReader {
|
||||
private int mIconId;
|
||||
private int mFlags;
|
||||
|
||||
private JSONObject mAppKeys = null;
|
||||
|
||||
public PBWReader(Uri uri, Context context, String platform) throws FileNotFoundException {
|
||||
this.uri = uri;
|
||||
cr = context.getContentResolver();
|
||||
@ -201,6 +203,10 @@ public class PBWReader {
|
||||
appCreator = json.getString("companyName");
|
||||
appVersion = json.getString("versionLabel");
|
||||
appUUID = UUID.fromString(json.getString("uuid"));
|
||||
if (json.has("appKeys")) {
|
||||
mAppKeys = json.getJSONObject("appKeys");
|
||||
LOG.info("found appKeys:" + mAppKeys.toString());
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
isValid = false;
|
||||
e.printStackTrace();
|
||||
@ -317,4 +323,8 @@ public class PBWReader {
|
||||
public int getIconId() {
|
||||
return mIconId;
|
||||
}
|
||||
|
||||
public JSONObject getAppKeysJSON() {
|
||||
return mAppKeys;
|
||||
}
|
||||
}
|
@ -2,11 +2,10 @@ package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AppManagerActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
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.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class PebbleCoordinator extends AbstractDeviceCoordinator {
|
||||
public PebbleCoordinator() {
|
||||
@ -45,13 +45,19 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
|
||||
|
||||
@Override
|
||||
public SampleProvider getSampleProvider() {
|
||||
// FIXME: make this configurable somewhere else.
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext());
|
||||
if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
|
||||
//return new PebbleGadgetBridgeSampleProvider();
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH);
|
||||
switch (activityTracker) {
|
||||
case SampleProvider.PROVIDER_PEBBLE_HEALTH:
|
||||
return new HealthSampleProvider();
|
||||
case SampleProvider.PROVIDER_PEBBLE_MISFIT:
|
||||
return new MisfitSampleProvider();
|
||||
} else {
|
||||
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() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAlarmConfiguration() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
return R.string.tap_connected_device_for_app_mananger;
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ public class PebbleGadgetBridgeSampleProvider extends MorpheuzSampleProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getID() {
|
||||
public int getID() {
|
||||
return SampleProvider.PROVIDER_PEBBLE_GADGETBRIDGE;
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,11 @@ import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class BluetoothStateChangeReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
@ -22,8 +21,8 @@ public class BluetoothStateChangeReceiver extends BroadcastReceiver {
|
||||
Intent refreshIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST);
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
|
||||
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (!sharedPrefs.getBoolean("general_autoconnectonbluetooth", false)) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (!prefs.getBoolean("general_autoconnectonbluetooth", false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,9 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.PowerManager;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -15,6 +13,7 @@ import org.slf4j.LoggerFactory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class K9Receiver extends BroadcastReceiver {
|
||||
|
||||
@ -24,11 +23,11 @@ public class K9Receiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if ("never".equals(sharedPrefs.getString("notification_mode_k9mail", "when_screen_off"))) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if ("never".equals(prefs.getString("notification_mode_k9mail", "when_screen_off"))) {
|
||||
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);
|
||||
if (powermanager.isScreenOn()) {
|
||||
return;
|
||||
@ -55,20 +54,9 @@ public class K9Receiver extends BroadcastReceiver {
|
||||
* It should be the first one returned by the query in most cases,
|
||||
*/
|
||||
|
||||
Cursor c = 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 {
|
||||
try (Cursor c = context.getContentResolver().query(k9Uri, messagesProjection, null, null, null)) {
|
||||
if (c != null) {
|
||||
c.moveToFirst();
|
||||
do {
|
||||
while (c.moveToNext()) {
|
||||
String uri = c.getString(c.getColumnIndex("uri"));
|
||||
if (uri.equals(uriWanted)) {
|
||||
notificationSpec.sender = c.getString(c.getColumnIndex("senderAddress"));
|
||||
@ -76,12 +64,13 @@ public class K9Receiver extends BroadcastReceiver {
|
||||
notificationSpec.body = c.getString(c.getColumnIndex("preview"));
|
||||
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);
|
||||
|
@ -8,20 +8,31 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
|
||||
public class MusicPlaybackReceiver extends BroadcastReceiver {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MusicPlaybackReceiver.class);
|
||||
|
||||
private static String mLastSource;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String artist = intent.getStringExtra("artist");
|
||||
String album = intent.getStringExtra("album");
|
||||
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);
|
||||
|
||||
GBApplication.deviceService().onSetMusicInfo(artist, album, track);
|
||||
MusicSpec musicSpec = new MusicSpec();
|
||||
musicSpec.artist = artist;
|
||||
musicSpec.artist = album;
|
||||
musicSpec.artist = track;
|
||||
|
||||
GBApplication.deviceService().onSetMusicInfo(musicSpec);
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,10 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
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.service.DeviceCommunicationService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class NotificationListener extends NotificationListenerService {
|
||||
|
||||
@ -54,6 +53,9 @@ public class NotificationListener extends NotificationListenerService {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case GBApplication.ACTION_QUIT:
|
||||
stopSelf();
|
||||
break;
|
||||
case ACTION_MUTE:
|
||||
case ACTION_OPEN: {
|
||||
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
|
||||
@ -130,6 +132,7 @@ public class NotificationListener extends NotificationListenerService {
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(ACTION_OPEN);
|
||||
filterLocal.addAction(ACTION_DISMISS);
|
||||
filterLocal.addAction(ACTION_DISMISS_ALL);
|
||||
@ -157,8 +160,8 @@ public class NotificationListener extends NotificationListenerService {
|
||||
return;
|
||||
}
|
||||
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (!sharedPrefs.getBoolean("notifications_generic_whenscreenon", false)) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
|
||||
PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
if (powermanager.isScreenOn()) {
|
||||
return;
|
||||
@ -185,13 +188,13 @@ public class NotificationListener extends NotificationListenerService {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -201,7 +204,7 @@ public class NotificationListener extends NotificationListenerService {
|
||||
source.equals("com.sonyericsson.conversations") ||
|
||||
source.equals("com.android.messaging") ||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.PowerManager;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@ -15,6 +13,7 @@ import org.slf4j.LoggerFactory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class PebbleReceiver extends BroadcastReceiver {
|
||||
|
||||
@ -23,11 +22,11 @@ public class PebbleReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if ("never".equals(sharedPrefs.getString("notification_mode_pebblemsg", "when_screen_off"))) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if ("never".equals(prefs.getString("notification_mode_pebblemsg", "when_screen_off"))) {
|
||||
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);
|
||||
if (powermanager.isScreenOn()) {
|
||||
return;
|
||||
|
@ -3,12 +3,11 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
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 {
|
||||
@ -31,7 +30,6 @@ public class PhoneCallReceiver extends BroadcastReceiver {
|
||||
} else if (TelephonyManager.EXTRA_STATE_RINGING.equals(stateStr)) {
|
||||
state = TelephonyManager.CALL_STATE_RINGING;
|
||||
}
|
||||
|
||||
onCallStateChanged(context, state, number);
|
||||
}
|
||||
}
|
||||
@ -41,34 +39,38 @@ public class PhoneCallReceiver extends BroadcastReceiver {
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceCommand callCommand = null;
|
||||
int callCommand = CallSpec.CALL_UNDEFINED;
|
||||
switch (state) {
|
||||
case TelephonyManager.CALL_STATE_RINGING:
|
||||
mSavedNumber = number;
|
||||
callCommand = ServiceCommand.CALL_INCOMING;
|
||||
callCommand = CallSpec.CALL_INCOMING;
|
||||
break;
|
||||
case TelephonyManager.CALL_STATE_OFFHOOK:
|
||||
if (mLastState == TelephonyManager.CALL_STATE_RINGING) {
|
||||
callCommand = ServiceCommand.CALL_START;
|
||||
callCommand = CallSpec.CALL_START;
|
||||
} else {
|
||||
callCommand = ServiceCommand.CALL_OUTGOING;
|
||||
callCommand = CallSpec.CALL_OUTGOING;
|
||||
mSavedNumber = number;
|
||||
}
|
||||
break;
|
||||
case TelephonyManager.CALL_STATE_IDLE:
|
||||
if (mLastState == TelephonyManager.CALL_STATE_RINGING) {
|
||||
//missed call would be correct here
|
||||
callCommand = ServiceCommand.CALL_END;
|
||||
callCommand = CallSpec.CALL_END;
|
||||
} else {
|
||||
callCommand = ServiceCommand.CALL_END;
|
||||
callCommand = CallSpec.CALL_END;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (callCommand != null) {
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if ("never".equals(sharedPrefs.getString("notification_mode_calls", "always"))) {
|
||||
if (callCommand != CallSpec.CALL_UNDEFINED) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if ("never".equals(prefs.getString("notification_mode_calls", "always"))) {
|
||||
return;
|
||||
}
|
||||
GBApplication.deviceService().onSetCallState(mSavedNumber, null, callCommand);
|
||||
CallSpec callSpec = new CallSpec();
|
||||
callSpec.number = mSavedNumber;
|
||||
callSpec.command = callCommand;
|
||||
GBApplication.deviceService().onSetCallState(callSpec);
|
||||
}
|
||||
mLastState = state;
|
||||
}
|
||||
|
@ -3,28 +3,24 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.telephony.SmsMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class SMSReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if ("never".equals(sharedPrefs.getString("notification_mode_sms", "when_screen_off"))) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if ("never".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) {
|
||||
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);
|
||||
if (powermanager.isScreenOn()) {
|
||||
return;
|
||||
|
@ -3,8 +3,6 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -14,6 +12,7 @@ import java.util.GregorianCalendar;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
|
||||
public class TimeChangeReceiver extends BroadcastReceiver {
|
||||
@ -22,10 +21,10 @@ public class TimeChangeReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
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();
|
||||
LOG.info("Time or Timezone changed, syncing with device: " + DateTimeUtils.formatDate(newTime) + " (" + newTime.toGMTString() + "), " + intent.getAction());
|
||||
GBApplication.deviceService().onSetTime();
|
||||
|
@ -2,32 +2,42 @@ package nodomain.freeyourgadget.gadgetbridge.impl;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
|
||||
public class GBActivitySample implements ActivitySample {
|
||||
private final int timestamp;
|
||||
private final SampleProvider provider;
|
||||
private final short intensity;
|
||||
private final short steps;
|
||||
private final byte type;
|
||||
private final int intensity;
|
||||
private final int steps;
|
||||
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.provider = provider;
|
||||
this.intensity = intensity;
|
||||
this.steps = steps;
|
||||
this.customValue = customValue;
|
||||
this.type = type;
|
||||
validate();
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
if (steps < 0) {
|
||||
throw new IllegalArgumentException("steps must be > 0");
|
||||
throw new IllegalArgumentException("steps must be >= 0");
|
||||
}
|
||||
if (intensity < 0) {
|
||||
throw new IllegalArgumentException("intensity must be > 0");
|
||||
throw new IllegalArgumentException("intensity must be >= 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
|
||||
public short getRawIntensity() {
|
||||
public int getRawIntensity() {
|
||||
return intensity;
|
||||
}
|
||||
|
||||
@ -52,12 +62,12 @@ public class GBActivitySample implements ActivitySample {
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getSteps() {
|
||||
public int getSteps() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getRawKind() {
|
||||
public int getRawKind() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@ -65,4 +75,20 @@ public class GBActivitySample implements ActivitySample {
|
||||
public int getKind() {
|
||||
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() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.impl;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Parcel;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.Calendar;
|
||||
@ -12,6 +10,7 @@ import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ALARMS;
|
||||
|
||||
@ -187,8 +186,8 @@ public class GBAlarm implements Alarm {
|
||||
}
|
||||
|
||||
public void store() {
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext());
|
||||
Set<String> preferencesAlarmListSet = sharedPrefs.getStringSet(PREF_MIBAND_ALARMS, new HashSet<String>());
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
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
|
||||
Set<String> newPrefs = new HashSet<>(preferencesAlarmListSet);
|
||||
|
||||
@ -202,7 +201,7 @@ public class GBAlarm implements Alarm {
|
||||
}
|
||||
}
|
||||
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() {
|
||||
|
@ -10,10 +10,16 @@ import android.support.v4.content.LocalBroadcastManager;
|
||||
import org.slf4j.Logger;
|
||||
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.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
|
||||
|
||||
public class GBDevice implements Parcelable {
|
||||
public static final String ACTION_DEVICE_CHANGED
|
||||
@ -34,6 +40,8 @@ public class GBDevice implements Parcelable {
|
||||
public static final short BATTERY_UNKNOWN = -1;
|
||||
private static final short BATTERY_THRESHOLD_PERCENT = 10;
|
||||
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 mAddress;
|
||||
private final DeviceType mDeviceType;
|
||||
@ -45,6 +53,7 @@ public class GBDevice implements Parcelable {
|
||||
private BatteryState mBatteryState;
|
||||
private short mRssi = RSSI_UNKNOWN;
|
||||
private String mBusyTask;
|
||||
private List<ItemWithDetails> mDeviceInfos;
|
||||
|
||||
public GBDevice(String address, String name, DeviceType deviceType) {
|
||||
mAddress = address;
|
||||
@ -65,6 +74,7 @@ public class GBDevice implements Parcelable {
|
||||
mBatteryState = (BatteryState) in.readSerializable();
|
||||
mRssi = (short) in.readInt();
|
||||
mBusyTask = in.readString();
|
||||
mDeviceInfos = in.readArrayList(getClass().getClassLoader());
|
||||
|
||||
validate();
|
||||
}
|
||||
@ -82,6 +92,7 @@ public class GBDevice implements Parcelable {
|
||||
dest.writeSerializable(mBatteryState);
|
||||
dest.writeInt(mRssi);
|
||||
dest.writeString(mBusyTask);
|
||||
dest.writeList(mDeviceInfos);
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
@ -192,35 +203,29 @@ public class GBDevice implements Parcelable {
|
||||
}
|
||||
|
||||
public String getStateString() {
|
||||
/*
|
||||
* for simplicity the user wont see all internal states, just connecting -> connected
|
||||
* instead of connecting->connected->initializing->initialized
|
||||
*/
|
||||
switch (mState) {
|
||||
case NOT_CONNECTED:
|
||||
return GBApplication.getContext().getString(R.string.not_connected);
|
||||
case WAITING_FOR_RECONNECT:
|
||||
return GBApplication.getContext().getString(R.string.waiting_for_reconnect);
|
||||
case CONNECTING:
|
||||
return GBApplication.getContext().getString(R.string.connecting);
|
||||
case CONNECTED:
|
||||
return GBApplication.getContext().getString(R.string.connected);
|
||||
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:
|
||||
return GBApplication.getContext().getString(R.string.initialized);
|
||||
return GBApplication.getContext().getString(R.string.connected);
|
||||
}
|
||||
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() {
|
||||
return mDeviceType;
|
||||
}
|
||||
@ -326,6 +331,49 @@ public class GBDevice implements Parcelable {
|
||||
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 {
|
||||
// Note: the order is important!
|
||||
NOT_CONNECTED,
|
||||
@ -333,6 +381,8 @@ public class GBDevice implements Parcelable {
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
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
|
||||
* have been performed. At the very least, this means that basic information like
|
||||
|
@ -12,6 +12,7 @@ public class GBDeviceApp {
|
||||
private final UUID uuid;
|
||||
private final Type type;
|
||||
private final boolean inCache;
|
||||
private final boolean configurable;
|
||||
|
||||
public GBDeviceApp(UUID uuid, String name, String creator, String version, Type type) {
|
||||
this.uuid = uuid;
|
||||
@ -21,9 +22,10 @@ public class GBDeviceApp {
|
||||
this.type = type;
|
||||
//FIXME: do not assume
|
||||
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");
|
||||
String name = "";
|
||||
String creator = "";
|
||||
@ -47,6 +49,7 @@ public class GBDeviceApp {
|
||||
this.type = type;
|
||||
//FIXME: do not assume
|
||||
this.inCache = true;
|
||||
this.configurable = configurable;
|
||||
}
|
||||
|
||||
public boolean isInCache() {
|
||||
@ -94,4 +97,8 @@ public class GBDeviceApp {
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
public boolean isConfigurable() {
|
||||
return configurable;
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,10 @@ import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
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.ServiceCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
|
||||
|
||||
public class GBDeviceService implements DeviceService {
|
||||
@ -115,19 +116,23 @@ public class GBDeviceService implements DeviceService {
|
||||
}
|
||||
|
||||
@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...
|
||||
Intent intent = createIntent().setAction(ACTION_CALLSTATE)
|
||||
.putExtra(EXTRA_CALL_PHONENUMBER, number)
|
||||
.putExtra(EXTRA_CALL_COMMAND, command);
|
||||
.putExtra(EXTRA_CALL_PHONENUMBER, callSpec.number)
|
||||
.putExtra(EXTRA_CALL_COMMAND, callSpec.command);
|
||||
invokeService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicInfo(String artist, String album, String track) {
|
||||
public void onSetMusicInfo(MusicSpec musicSpec) {
|
||||
Intent intent = createIntent().setAction(ACTION_SETMUSICINFO)
|
||||
.putExtra(EXTRA_MUSIC_ARTIST, artist)
|
||||
.putExtra(EXTRA_MUSIC_TRACK, track);
|
||||
.putExtra(EXTRA_MUSIC_ARTIST, musicSpec.artist)
|
||||
.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);
|
||||
}
|
||||
|
||||
@ -159,6 +164,14 @@ public class GBDeviceService implements DeviceService {
|
||||
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
|
||||
public void onFetchActivityData() {
|
||||
Intent intent = createIntent().setAction(ACTION_FETCH_ACTIVITY_DATA);
|
||||
@ -171,6 +184,12 @@ public class GBDeviceService implements DeviceService {
|
||||
invokeService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeartRateTest() {
|
||||
Intent intent = createIntent().setAction(ACTION_HEARTRATE_TEST);
|
||||
invokeService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean start) {
|
||||
Intent intent = createIntent().setAction(ACTION_FIND_DEVICE)
|
||||
@ -187,7 +206,21 @@ public class GBDeviceService implements DeviceService {
|
||||
@Override
|
||||
public void onEnableRealtimeSteps(boolean enable) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,8 @@ public class ActivityKind {
|
||||
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 byte[] mapToDBActivityTypes(int types, SampleProvider provider) {
|
||||
byte[] result = new byte[3];
|
||||
public static int[] mapToDBActivityTypes(int types, SampleProvider provider) {
|
||||
int[] result = new int[3];
|
||||
int i = 0;
|
||||
if ((types & ActivityKind.TYPE_ACTIVITY) != 0) {
|
||||
result[i++] = provider.toRawActivityKind(TYPE_ACTIVITY);
|
||||
|
@ -18,7 +18,7 @@ public interface ActivitySample {
|
||||
/**
|
||||
* 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
|
||||
@ -30,7 +30,7 @@ public interface ActivitySample {
|
||||
/**
|
||||
* Returns the raw intensity value as recorded by the SampleProvider
|
||||
*/
|
||||
short getRawIntensity();
|
||||
int getRawIntensity();
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
short getSteps();
|
||||
int getSteps();
|
||||
|
||||
int getCustomValue();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -54,11 +54,11 @@ public class CalendarEvents {
|
||||
ContentUris.appendId(eventsUriBuilder, dtEnd);
|
||||
Uri eventsUri = eventsUriBuilder.build();
|
||||
|
||||
Cursor evtCursor = null;
|
||||
evtCursor = mContext.getContentResolver().query(eventsUri, EVENT_INSTANCE_PROJECTION, null, null, CalendarContract.Instances.BEGIN + " ASC");
|
||||
|
||||
if (evtCursor.moveToFirst()) {
|
||||
do {
|
||||
try (Cursor evtCursor = mContext.getContentResolver().query(eventsUri, EVENT_INSTANCE_PROJECTION, null, null, CalendarContract.Instances.BEGIN + " ASC")) {
|
||||
if (evtCursor == null || evtCursor.getCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
while (evtCursor.moveToNext()) {
|
||||
CalendarEvent calEvent = new CalendarEvent(
|
||||
evtCursor.getLong(1),
|
||||
evtCursor.getLong(2),
|
||||
@ -69,11 +69,9 @@ public class CalendarEvents {
|
||||
evtCursor.getString(7)
|
||||
);
|
||||
calendarEventList.add(calEvent);
|
||||
} while(evtCursor.moveToNext());
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public class CalendarEvent {
|
||||
|
@ -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;
|
||||
}
|
@ -15,7 +15,6 @@ public interface DeviceService extends EventHandler {
|
||||
String ACTION_START = PREFIX + ".action.start";
|
||||
String ACTION_CONNECT = PREFIX + ".action.connect";
|
||||
String ACTION_NOTIFICATION = PREFIX + ".action.notification";
|
||||
String ACTION_NOTIFICATION_SMS = PREFIX + ".action.notification_sms";
|
||||
String ACTION_CALLSTATE = PREFIX + ".action.callstate";
|
||||
String ACTION_SETTIME = PREFIX + ".action.settime";
|
||||
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_STARTAPP = PREFIX + ".action.startapp";
|
||||
String ACTION_DELETEAPP = PREFIX + ".action.deleteapp";
|
||||
String ACTION_APP_CONFIGURE = PREFIX + ".action.app_configure";
|
||||
String ACTION_INSTALL = PREFIX + ".action.install";
|
||||
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_DISCONNECT = PREFIX + ".action.disconnect";
|
||||
String ACTION_FIND_DEVICE = PREFIX + ".action.find_device";
|
||||
String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms";
|
||||
String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_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_NOTIFICATION_BODY = "notification_body";
|
||||
String EXTRA_NOTIFICATION_FLAGS = "notification_flags";
|
||||
@ -49,14 +52,19 @@ public interface DeviceService extends EventHandler {
|
||||
String EXTRA_MUSIC_ARTIST = "music_artist";
|
||||
String EXTRA_MUSIC_ALBUM = "music_album";
|
||||
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_START = "app_start";
|
||||
String EXTRA_APP_CONFIG = "app_config";
|
||||
String EXTRA_URI = "uri";
|
||||
String EXTRA_ALARMS = "alarms";
|
||||
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_TIMESTAMP = "timestamp";
|
||||
String EXTRA_HEART_RATE_VALUE = "hr_value";
|
||||
|
||||
void start();
|
||||
|
||||
|
@ -1,10 +1,31 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.text.Collator;
|
||||
|
||||
public class GenericItem implements ItemWithDetails {
|
||||
private String name;
|
||||
private String details;
|
||||
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) {
|
||||
this.name = name;
|
||||
this.details = details;
|
||||
@ -17,6 +38,13 @@ public class GenericItem implements ItemWithDetails {
|
||||
public GenericItem() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(getName());
|
||||
dest.writeString(getDetails());
|
||||
dest.writeInt(getIcon());
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
@ -43,4 +71,38 @@ public class GenericItem implements ItemWithDetails {
|
||||
public int getIcon() {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,18 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
public interface ItemWithDetails {
|
||||
import android.os.Parcelable;
|
||||
|
||||
public interface ItemWithDetails extends Parcelable, Comparable<ItemWithDetails> {
|
||||
String getName();
|
||||
|
||||
String getDetails();
|
||||
|
||||
int getIcon();
|
||||
|
||||
/**
|
||||
* Equality is based on #getName() only.
|
||||
*
|
||||
* @param other
|
||||
*/
|
||||
boolean equals(Object other);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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
|
||||
}
|
@ -6,11 +6,9 @@ import android.app.PendingIntent;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
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.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
|
||||
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.GBMusicControlReceiver;
|
||||
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?
|
||||
// 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;
|
||||
private BluetoothAdapter btAdapter;
|
||||
private Context context;
|
||||
private boolean autoReconnect;
|
||||
|
||||
public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
|
||||
this.gbDevice = gbDevice;
|
||||
@ -81,6 +82,16 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
|
||||
return gbDevice.isInitialized();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAutoReconnect(boolean enable) {
|
||||
autoReconnect = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAutoReconnect() {
|
||||
return autoReconnect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GBDevice getDevice() {
|
||||
return gbDevice;
|
||||
@ -246,8 +257,8 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
|
||||
Intent notificationListenerIntent = new Intent(action);
|
||||
notificationListenerIntent.putExtra("handle", deviceEvent.handle);
|
||||
if (deviceEvent.reply != null) {
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext());
|
||||
String suffix = sharedPrefs.getString("canned_reply_suffix", null);
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
String suffix = prefs.getString("canned_reply_suffix", null);
|
||||
if (suffix != null && !Objects.equals(suffix, "")) {
|
||||
deviceEvent.reply += suffix;
|
||||
}
|
||||
@ -280,4 +291,14 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package nodomain.freeyourgadget.gadgetbridge.service;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
@ -11,7 +10,6 @@ import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.annotation.Nullable;
|
||||
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.impl.GBDevice;
|
||||
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.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
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_CONNECT;
|
||||
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_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_FETCH_ACTIVITY_DATA;
|
||||
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_NOTIFICATION;
|
||||
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_STARTAPP;
|
||||
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_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_PHONENUMBER;
|
||||
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_MUSIC_ALBUM;
|
||||
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_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_FLAGS;
|
||||
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_URI;
|
||||
|
||||
public class DeviceCommunicationService extends Service {
|
||||
public class DeviceCommunicationService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class);
|
||||
|
||||
private boolean mStarted = false;
|
||||
@ -109,7 +118,7 @@ public class DeviceCommunicationService extends Service {
|
||||
mGBDevice = device;
|
||||
boolean enableReceivers = mDeviceSupport != null && (mDeviceSupport.useAutoConnect() || mGBDevice.isInitialized());
|
||||
setReceiversEnableState(enableReceivers);
|
||||
GB.updateNotification(mGBDevice.getName() + " " + mGBDevice.getStateString(), context);
|
||||
GB.updateNotification(mGBDevice.getName() + " " + mGBDevice.getStateString(), mGBDevice.isInitialized(), context);
|
||||
} else {
|
||||
LOG.error("Got ACTION_DEVICE_CHANGED from unexpected device: " + mGBDevice);
|
||||
}
|
||||
@ -123,6 +132,10 @@ public class DeviceCommunicationService extends Service {
|
||||
super.onCreate();
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
|
||||
mFactory = new DeviceSupportFactory(this);
|
||||
|
||||
if (hasPrefs()) {
|
||||
getPrefs().getPreferences().registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -162,6 +175,7 @@ public class DeviceCommunicationService extends Service {
|
||||
|
||||
// when we get past this, we should have valid mDeviceSupport and mGBDevice instances
|
||||
|
||||
Prefs prefs = getPrefs();
|
||||
switch (action) {
|
||||
case ACTION_START:
|
||||
start();
|
||||
@ -169,19 +183,23 @@ public class DeviceCommunicationService extends Service {
|
||||
case ACTION_CONNECT:
|
||||
start(); // ensure started
|
||||
GBDevice gbDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
String btDeviceAddress = null;
|
||||
if (gbDevice == null) {
|
||||
String btDeviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS);
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (sharedPrefs != null) { // may be null in test cases
|
||||
if (btDeviceAddress == null) {
|
||||
btDeviceAddress = sharedPrefs.getString("last_device_address", null);
|
||||
} else {
|
||||
sharedPrefs.edit().putString("last_device_address", btDeviceAddress).apply();
|
||||
}
|
||||
btDeviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS);
|
||||
if (btDeviceAddress == null && prefs != null) { // may be null in test cases
|
||||
btDeviceAddress = prefs.getString("last_device_address", null);
|
||||
}
|
||||
if (btDeviceAddress != null) {
|
||||
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()) {
|
||||
@ -193,6 +211,7 @@ public class DeviceCommunicationService extends Service {
|
||||
if (pair) {
|
||||
deviceSupport.pair();
|
||||
} else {
|
||||
deviceSupport.setAutoReconnect(autoReconnect);
|
||||
deviceSupport.connect();
|
||||
}
|
||||
} else {
|
||||
@ -230,13 +249,12 @@ public class DeviceCommunicationService extends Service {
|
||||
if (((notificationSpec.flags & NotificationSpec.FLAG_WEARABLE_REPLY) > 0)
|
||||
|| (notificationSpec.type == NotificationType.SMS && notificationSpec.phoneNumber != null)) {
|
||||
// NOTE: maybe not where it belongs
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
|
||||
if (prefs.getBoolean("pebble_force_untested", false)) {
|
||||
// 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
|
||||
ArrayList<String> replies = new ArrayList<>();
|
||||
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("")) {
|
||||
replies.add(reply);
|
||||
}
|
||||
@ -251,6 +269,10 @@ public class DeviceCommunicationService extends Service {
|
||||
mDeviceSupport.onReboot();
|
||||
break;
|
||||
}
|
||||
case ACTION_HEARTRATE_TEST: {
|
||||
mDeviceSupport.onHeartRateTest();
|
||||
break;
|
||||
}
|
||||
case ACTION_FETCH_ACTIVITY_DATA: {
|
||||
mDeviceSupport.onFetchActivityData();
|
||||
break;
|
||||
@ -266,23 +288,32 @@ public class DeviceCommunicationService extends Service {
|
||||
break;
|
||||
}
|
||||
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 callerName = null;
|
||||
if (phoneNumber != null) {
|
||||
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;
|
||||
case ACTION_SETTIME:
|
||||
mDeviceSupport.onSetTime();
|
||||
break;
|
||||
case ACTION_SETMUSICINFO:
|
||||
String artist = intent.getStringExtra(EXTRA_MUSIC_ARTIST);
|
||||
String album = intent.getStringExtra(EXTRA_MUSIC_ALBUM);
|
||||
String track = intent.getStringExtra(EXTRA_MUSIC_TRACK);
|
||||
mDeviceSupport.onSetMusicInfo(artist, album, track);
|
||||
MusicSpec musicSpec = new MusicSpec();
|
||||
musicSpec.artist = intent.getStringExtra(EXTRA_MUSIC_ARTIST);
|
||||
musicSpec.album = intent.getStringExtra(EXTRA_MUSIC_ALBUM);
|
||||
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;
|
||||
case ACTION_REQUEST_APPINFO:
|
||||
mDeviceSupport.onAppInfoReq();
|
||||
@ -301,6 +332,11 @@ public class DeviceCommunicationService extends Service {
|
||||
mDeviceSupport.onAppDelete(uuid);
|
||||
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:
|
||||
Uri uri = intent.getParcelableExtra(EXTRA_URI);
|
||||
if (uri != null) {
|
||||
@ -312,11 +348,22 @@ public class DeviceCommunicationService extends Service {
|
||||
ArrayList<Alarm> alarms = intent.getParcelableArrayListExtra(EXTRA_ALARMS);
|
||||
mDeviceSupport.onSetAlarms(alarms);
|
||||
break;
|
||||
case ACTION_ENABLE_REALTIME_STEPS:
|
||||
boolean enable = intent.getBooleanExtra(EXTRA_ENABLE_REALTIME_STEPS, false);
|
||||
case ACTION_ENABLE_REALTIME_STEPS: {
|
||||
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
|
||||
mDeviceSupport.onEnableRealtimeSteps(enable);
|
||||
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;
|
||||
}
|
||||
@ -348,7 +395,7 @@ public class DeviceCommunicationService extends Service {
|
||||
|
||||
private void start() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -398,7 +445,10 @@ public class DeviceCommunicationService extends Service {
|
||||
}
|
||||
if (mMusicPlaybackReceiver == null) {
|
||||
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) {
|
||||
mTimeChangeReceiver = new TimeChangeReceiver();
|
||||
@ -437,6 +487,10 @@ public class DeviceCommunicationService extends Service {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (hasPrefs()) {
|
||||
getPrefs().getPreferences().unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
LOG.debug("DeviceCommunicationService is being destroyed");
|
||||
super.onDestroy();
|
||||
|
||||
@ -462,26 +516,37 @@ public class DeviceCommunicationService extends Service {
|
||||
return name;
|
||||
}
|
||||
|
||||
ContentResolver contentResolver = getContentResolver();
|
||||
|
||||
Cursor contactLookup = null;
|
||||
try {
|
||||
contactLookup = contentResolver.query(uri, null, null, null, null);
|
||||
} catch (SecurityException e) {
|
||||
return name;
|
||||
}
|
||||
|
||||
try {
|
||||
try (Cursor contactLookup = getContentResolver().query(uri, null, null, null, null)) {
|
||||
if (contactLookup != null && contactLookup.getCount() > 0) {
|
||||
contactLookup.moveToNext();
|
||||
name = contactLookup.getString(contactLookup.getColumnIndex(ContactsContract.Data.DISPLAY_NAME));
|
||||
}
|
||||
} finally {
|
||||
if (contactLookup != null) {
|
||||
contactLookup.close();
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
// ignore, just return name below
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,20 @@ public interface DeviceSupport extends EventHandler {
|
||||
*/
|
||||
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
|
||||
* will be reported via a device change Intent.
|
||||
|
@ -1,7 +1,6 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -11,10 +10,8 @@ import java.util.EnumSet;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
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.pebble.PebbleSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class DeviceSupportFactory {
|
||||
|
@ -13,8 +13,9 @@ import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
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.ServiceCommand;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAutoReconnect(boolean enable) {
|
||||
delegate.setAutoReconnect(enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAutoReconnect() {
|
||||
return delegate.getAutoReconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
delegate.dispose();
|
||||
@ -131,19 +142,19 @@ public class ServiceDeviceSupport implements DeviceSupport {
|
||||
// No throttling for the other events
|
||||
|
||||
@Override
|
||||
public void onSetCallState(String number, String name, ServiceCommand command) {
|
||||
public void onSetCallState(CallSpec callSpec) {
|
||||
if (checkBusy("set call state")) {
|
||||
return;
|
||||
}
|
||||
delegate.onSetCallState(number, name, command);
|
||||
delegate.onSetCallState(callSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicInfo(String artist, String album, String track) {
|
||||
public void onSetMusicInfo(MusicSpec musicSpec) {
|
||||
if (checkBusy("set music info")) {
|
||||
return;
|
||||
}
|
||||
delegate.onSetMusicInfo(artist, album, track);
|
||||
delegate.onSetMusicInfo(musicSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -178,6 +189,14 @@ public class ServiceDeviceSupport implements DeviceSupport {
|
||||
delegate.onAppDelete(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppConfiguration(UUID uuid, String config) {
|
||||
if (checkBusy("app configuration")) {
|
||||
return;
|
||||
}
|
||||
delegate.onAppConfiguration(uuid, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchActivityData() {
|
||||
if (checkBusy("fetch activity data")) {
|
||||
@ -194,6 +213,14 @@ public class ServiceDeviceSupport implements DeviceSupport {
|
||||
delegate.onReboot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeartRateTest() {
|
||||
if (checkBusy("heartrate")) {
|
||||
return;
|
||||
}
|
||||
delegate.onHeartRateTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean start) {
|
||||
if (checkBusy("find device")) {
|
||||
@ -225,4 +252,20 @@ public class ServiceDeviceSupport implements DeviceSupport {
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
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
|
||||
@ -41,10 +42,19 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
public boolean connect() {
|
||||
if (mQueue == null) {
|
||||
mQueue = new BtLEQueue(getBluetoothAdapter(), getDevice(), this, getContext());
|
||||
mQueue.setAutoReconnect(getAutoReconnect());
|
||||
}
|
||||
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).
|
||||
*
|
||||
|
@ -36,6 +36,7 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
|
||||
* Performs this operation. The whole operation is asynchronous, i.e.
|
||||
* this method quickly returns before the actual operation is finished.
|
||||
* Calls #prePerform() and, if successful, #doPerform().
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
@ -48,6 +49,7 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
|
||||
|
||||
/**
|
||||
* Hook for subclasses to perform something before #doPerform() is invoked.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void prePerform() throws IOException {
|
||||
@ -58,6 +60,7 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
|
||||
* successfully.
|
||||
* Note that subclasses HAVE TO call #operationFinished() when the entire
|
||||
* opreation is done (successful or not).
|
||||
*
|
||||
* @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
|
||||
* successfull or unsuccessfully.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void operationFinished() throws IOException {
|
||||
@ -109,6 +113,10 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
|
||||
return operationStatus == OperationStatus.RUNNING;
|
||||
}
|
||||
|
||||
public boolean isOperationFinished() {
|
||||
return operationStatus == OperationStatus.FINISHED;
|
||||
}
|
||||
|
||||
public T getSupport() {
|
||||
return mSupport;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user