mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-28 04:46:51 +01:00
Merge branch 'master' of github.com:Freeyourgadget/Gadgetbridge into fossil-q-hybrid
This commit is contained in:
commit
b20963348d
19
.github/ISSUE_TEMPLATE.md
vendored
19
.github/ISSUE_TEMPLATE.md
vendored
@ -1,19 +0,0 @@
|
|||||||
#### Before opening an issue please confirm the following:
|
|
||||||
- [ ] I have read the [wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki), and I didn't find a solution to my problem / an answer to my question.
|
|
||||||
- [ ] I have searched the [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
|
|
||||||
- [ ] If you upload an image or other content, please make sure you have read and understood the [github policies and terms of services](https://help.github.com/articles/github-terms-of-service/#1-responsibility-for-user-generated-content)
|
|
||||||
|
|
||||||
#### Your issue is:
|
|
||||||
*In case of a bug, do not forget to attach logs!*
|
|
||||||
|
|
||||||
#### Your wearable device is:
|
|
||||||
|
|
||||||
*Please specify model and firmware version if possible*
|
|
||||||
|
|
||||||
#### Your android version is:
|
|
||||||
|
|
||||||
#### Your Gadgetbridge version is:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*New issues about already solved/documented topics could be closed without further comments. Same for too generic or incomplete reports.*
|
|
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -9,6 +9,12 @@ about: Create a report to help us improve
|
|||||||
- [ ] I have searched the [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
|
- [ ] I have searched the [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues), and I didn't find a solution to my problem / an answer to my question.
|
||||||
- [ ] If you upload an image or other content, please make sure you have read and understood the [github policies and terms of services](https://help.github.com/articles/github-terms-of-service/#1-responsibility-for-user-generated-content)
|
- [ ] If you upload an image or other content, please make sure you have read and understood the [github policies and terms of services](https://help.github.com/articles/github-terms-of-service/#1-responsibility-for-user-generated-content)
|
||||||
|
|
||||||
|
### I got Gadgetbridge from:
|
||||||
|
* [ ] F-Droid
|
||||||
|
* [ ] I built it myself from source code (specify tag / commit)
|
||||||
|
|
||||||
|
If you got it from Google Play, please note [that version](https://github.com/TaaviE/Gadgetbridge) is unofficial and not supported here; it's also often quite outdated. Please switch to one of the above versions if you can.
|
||||||
|
|
||||||
#### Your issue is:
|
#### Your issue is:
|
||||||
*If possible, please attach [logs](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Log-Files)! that might help identifying the problem.*
|
*If possible, please attach [logs](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Log-Files)! that might help identifying the problem.*
|
||||||
|
|
||||||
|
35
CHANGELOG.md
35
CHANGELOG.md
@ -1,7 +1,40 @@
|
|||||||
### Changelog
|
### Changelog
|
||||||
|
|
||||||
|
#### Version 0.36.2
|
||||||
|
* Amazfit Bip: Untested support for Lite variant
|
||||||
|
* Force Lineage OS to ask for permission when Trust is used to fix non-working incoming calls
|
||||||
|
* Charts: List multiple sleep sessions per day
|
||||||
|
|
||||||
|
#### Version 0.36.1
|
||||||
|
* Mi Band 2/3/4, Amazfit Bip/Cor: Add setting to expose the HR sensor to 3rd party apps
|
||||||
|
* Mi Band 4: Really fix weather location not being updated on the Band
|
||||||
|
* Mi Band 4: Fix call notifcation not stopping when call gets answered or rejected on the phone
|
||||||
|
* Amazfit Bip/Cor: Support for custom emoji font
|
||||||
|
* ZeTime: Enable emoji support
|
||||||
|
* ZeTime: Make watch language the same as the phone language by default
|
||||||
|
* New status and alarms widget
|
||||||
|
* Fix crash when entering notification filter settings
|
||||||
|
* Make diagram settings accessible from charts activity
|
||||||
|
* Add option to hide the floating plus button in the main activity
|
||||||
|
* Fix a potential crash on Android 4.4 KitKat
|
||||||
|
|
||||||
|
#### Version 0.36.0
|
||||||
|
* Initial Mijia LYWSD02 support (Smart Clock with Humidity and Temperature Sensor), just for setting the time
|
||||||
|
* Mi Band 3/4: Allow enabling the NFC menu where supported (useless for now)
|
||||||
|
* Mi Band 3/4, Amazfit Cor/Bip: Set language immediately when changing it (not only on connect)
|
||||||
|
* Mi Band 3/4, Amazfir Cor/Bip: Add icons for "swimming" and "exercise"
|
||||||
|
* Mi Band 4: Support flashing the V2 font
|
||||||
|
* Mi Band 4: Fix weather location not being updated on the Band
|
||||||
|
* Mi Band 4: remove unsupported DND setting from settings menu
|
||||||
|
* Amazfit Bip/Cor: Fix resetting of last fetched date for sports activities
|
||||||
|
* Amazfit Bip: Fix sharing GPX files for some Apps
|
||||||
|
* Pebble: Use Rebble Store URI
|
||||||
|
* Support LineageOS 16.0 weather provider
|
||||||
|
* Add Averages to Charts
|
||||||
|
* Allow togging between weekly and monthly charts
|
||||||
|
|
||||||
#### Version 0.35.2
|
#### Version 0.35.2
|
||||||
* Mi Band 1/2: Crash when updating firemare while phone is set to Spanish
|
* Mi Band 1/2: Crash when updating firmware while phone is set to Spanish
|
||||||
* Mi Band 4: Enable music info support (displays now on the band)
|
* Mi Band 4: Enable music info support (displays now on the band)
|
||||||
* Mi Band 4: Support setting date format (for built-in watchfaces)
|
* Mi Band 4: Support setting date format (for built-in watchfaces)
|
||||||
* Amazfit Cor 2: Try to fix empty menu on device
|
* Amazfit Cor 2: Try to fix empty menu on device
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
apply plugin: "com.android.application"
|
apply plugin: "com.android.application"
|
||||||
apply plugin: "findbugs"
|
apply plugin: "com.github.spotbugs"
|
||||||
apply plugin: "pmd"
|
apply plugin: "pmd"
|
||||||
|
|
||||||
def ABORT_ON_CHECK_FAILURE = false
|
def ABORT_ON_CHECK_FAILURE = false
|
||||||
@ -25,8 +25,8 @@ android {
|
|||||||
targetSdkVersion 27
|
targetSdkVersion 27
|
||||||
|
|
||||||
// Note: always bump BOTH versionCode and versionName!
|
// Note: always bump BOTH versionCode and versionName!
|
||||||
versionName "0.35.2"
|
versionName "0.36.2"
|
||||||
versionCode 154
|
versionCode 157
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@ -66,8 +66,9 @@ dependencies {
|
|||||||
testImplementation "org.robolectric:robolectric:4.2.1"
|
testImplementation "org.robolectric:robolectric:4.2.1"
|
||||||
testImplementation "com.google.code.gson:gson:2.8.5"
|
testImplementation "com.google.code.gson:gson:2.8.5"
|
||||||
|
|
||||||
implementation "androidx.appcompat:appcompat:1.0.2"
|
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||||
implementation "androidx.preference:preference:1.1.0-alpha05"
|
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||||
|
implementation "androidx.preference:preference:1.1.0"
|
||||||
implementation "androidx.cardview:cardview:1.0.0"
|
implementation "androidx.cardview:cardview:1.0.0"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.0.0"
|
implementation "androidx.recyclerview:recyclerview:1.0.0"
|
||||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||||
@ -78,7 +79,7 @@ dependencies {
|
|||||||
exclude group: "com.google.android", module: "android"
|
exclude group: "com.google.android", module: "android"
|
||||||
}
|
}
|
||||||
implementation "org.slf4j:slf4j-api:1.7.12"
|
implementation "org.slf4j:slf4j-api:1.7.12"
|
||||||
implementation "com.github.Freeyourgadget:MPAndroidChart:5e5bd6c1d3e95c515d4853647ae554e48ee1d593"
|
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||||
implementation "com.github.pfichtner:durationformatter:0.1.1"
|
implementation "com.github.pfichtner:durationformatter:0.1.1"
|
||||||
implementation "de.cketti.library.changelog:ckchangelog:1.2.2"
|
implementation "de.cketti.library.changelog:ckchangelog:1.2.2"
|
||||||
implementation "net.e175.klaus:solarpositioning:0.0.9"
|
implementation "net.e175.klaus:solarpositioning:0.0.9"
|
||||||
@ -107,7 +108,7 @@ gradle.beforeProject {
|
|||||||
preBuild.dependsOn(":GBDaoGenerator:genSources")
|
preBuild.dependsOn(":GBDaoGenerator:genSources")
|
||||||
}
|
}
|
||||||
|
|
||||||
check.dependsOn "findbugs", "pmd", "lint"
|
check.dependsOn "spotbugsMain", "pmd", "lint"
|
||||||
|
|
||||||
task pmd(type: Pmd) {
|
task pmd(type: Pmd) {
|
||||||
ruleSetFiles = files("${project.rootDir}/config/pmd/pmd-ruleset.xml")
|
ruleSetFiles = files("${project.rootDir}/config/pmd/pmd-ruleset.xml")
|
||||||
@ -150,22 +151,32 @@ task pmd(type: Pmd) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task findbugs(type: FindBugs) {
|
// this is just for spotbugs to let the plugin create the task
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java.srcDirs = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spotbugs {
|
||||||
|
toolVersion = "3.1.12"
|
||||||
ignoreFailures = !ABORT_ON_CHECK_FAILURE
|
ignoreFailures = !ABORT_ON_CHECK_FAILURE
|
||||||
effort = "default"
|
effort = "default"
|
||||||
reportLevel = "medium"
|
reportLevel = "medium"
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(com.github.spotbugs.SpotBugsTask) {
|
||||||
|
source = fileTree('src/main/java')
|
||||||
|
classes = files("${project.rootDir}/app/build/intermediates/javac/debug/classes")
|
||||||
excludeFilter = new File("${project.rootDir}/config/findbugs/findbugs-filter.xml")
|
excludeFilter = new File("${project.rootDir}/config/findbugs/findbugs-filter.xml")
|
||||||
classes = files("${project.rootDir}/app/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes")
|
|
||||||
source = fileTree("src/main/java/")
|
|
||||||
classpath = files()
|
|
||||||
reports {
|
reports {
|
||||||
xml.enabled = false
|
xml.enabled = false
|
||||||
html.enabled = true
|
html.enabled = true
|
||||||
xml {
|
xml {
|
||||||
destination file ("$project.buildDir/reports/findbugs/findbugs-output.xml")
|
destination file ("$project.buildDir/reports/spotbugs/spotbugs-output.xml")
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
destination file ("$project.buildDir/reports/findbugs/findbugs-output.html")
|
destination file ("$project.buildDir/reports/spotbugs/spotbugs-output.html")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
|
|
||||||
<uses-permission android:name="cyanogenmod.permission.ACCESS_WEATHER_MANAGER" />
|
<uses-permission android:name="cyanogenmod.permission.ACCESS_WEATHER_MANAGER" />
|
||||||
<uses-permission android:name="cyanogenmod.permission.READ_WEATHER" />
|
<uses-permission android:name="cyanogenmod.permission.READ_WEATHER" />
|
||||||
|
<uses-permission android:name="lineageos.permission.ACCESS_WEATHER_MANAGER" />
|
||||||
|
<uses-permission android:name="lineageos.permission.READ_WEATHER" />
|
||||||
<uses-permission android:name="org.omnirom.omnijaws.READ_WEATHER" />
|
<uses-permission android:name="org.omnirom.omnijaws.READ_WEATHER" />
|
||||||
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
@ -58,6 +60,10 @@
|
|||||||
android:name=".activities.SettingsActivity"
|
android:name=".activities.SettingsActivity"
|
||||||
android:label="@string/title_activity_settings"
|
android:label="@string/title_activity_settings"
|
||||||
android:parentActivityName=".activities.ControlCenterv2" />
|
android:parentActivityName=".activities.ControlCenterv2" />
|
||||||
|
<activity
|
||||||
|
android:name=".activities.charts.ChartsPreferencesActivity"
|
||||||
|
android:label="@string/activity_prefs_charts"
|
||||||
|
android:parentActivityName=".activities.charts.ChartsPreferencesActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".devices.miband.MiBandPreferencesActivity"
|
android:name=".devices.miband.MiBandPreferencesActivity"
|
||||||
android:label="@string/preferences_miband_settings"
|
android:label="@string/preferences_miband_settings"
|
||||||
@ -443,7 +449,8 @@
|
|||||||
android:resource="@xml/shared_paths" />
|
android:resource="@xml/shared_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<receiver android:name=".SleepAlarmWidget">
|
<receiver android:name=".SleepAlarmWidget"
|
||||||
|
android:label="@string/appwidget_sleep_alarm_widget_label">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
<action android:name="nodomain.freeyourgadget.gadgetbridge.SLEEP_ALARM_WIDGET_CLICK" />
|
<action android:name="nodomain.freeyourgadget.gadgetbridge.SLEEP_ALARM_WIDGET_CLICK" />
|
||||||
@ -454,6 +461,26 @@
|
|||||||
android:resource="@xml/sleep_alarm_widget_info" />
|
android:resource="@xml/sleep_alarm_widget_info" />
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".Widget"
|
||||||
|
android:label="@string/widget_listing_label">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
<action android:name="nodomain.freeyourgadget.gadgetbridge.WidgetClick" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/widget_info" />
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activities.WidgetAlarmsActivity"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.Dialog"
|
||||||
|
android:excludeFromRecents="true"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:allowTaskReparenting="true"
|
android:allowTaskReparenting="true"
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weather;
|
||||||
|
|
||||||
|
import lineageos.weather.IWeatherServiceProviderChangeListener;
|
||||||
|
import lineageos.weather.RequestInfo;
|
||||||
|
|
||||||
|
interface ILineageWeatherManager {
|
||||||
|
oneway void updateWeather(in RequestInfo info);
|
||||||
|
oneway void lookupCity(in RequestInfo info);
|
||||||
|
oneway void registerWeatherServiceProviderChangeListener(
|
||||||
|
in IWeatherServiceProviderChangeListener listener);
|
||||||
|
oneway void unregisterWeatherServiceProviderChangeListener(
|
||||||
|
in IWeatherServiceProviderChangeListener listener);
|
||||||
|
String getActiveWeatherServiceProviderLabel();
|
||||||
|
oneway void cancelRequest(int requestId);
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weather;
|
||||||
|
|
||||||
|
import lineageos.weather.RequestInfo;
|
||||||
|
import lineageos.weather.WeatherInfo;
|
||||||
|
import lineageos.weather.WeatherLocation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
interface IRequestInfoListener {
|
||||||
|
void onWeatherRequestCompleted(in RequestInfo requestInfo, int status,
|
||||||
|
in WeatherInfo weatherInfo);
|
||||||
|
void onLookupCityRequestCompleted(in RequestInfo requestInfo, int status,
|
||||||
|
in List<WeatherLocation> weatherLocation);
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weather;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
oneway interface IWeatherServiceProviderChangeListener {
|
||||||
|
void onWeatherServiceProviderChanged(String providerLabel);
|
||||||
|
}
|
19
app/src/main/aidl/lineageos/weather/RequestInfo.aidl
Normal file
19
app/src/main/aidl/lineageos/weather/RequestInfo.aidl
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weather;
|
||||||
|
|
||||||
|
parcelable RequestInfo;
|
19
app/src/main/aidl/lineageos/weather/WeatherInfo.aidl
Normal file
19
app/src/main/aidl/lineageos/weather/WeatherInfo.aidl
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanongenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weather;
|
||||||
|
|
||||||
|
parcelable WeatherInfo;
|
19
app/src/main/aidl/lineageos/weather/WeatherLocation.aidl
Normal file
19
app/src/main/aidl/lineageos/weather/WeatherLocation.aidl
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weather;
|
||||||
|
|
||||||
|
parcelable WeatherLocation;
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weatherservice;
|
||||||
|
|
||||||
|
import lineageos.weatherservice.IWeatherProviderServiceClient;
|
||||||
|
import lineageos.weather.RequestInfo;
|
||||||
|
|
||||||
|
interface IWeatherProviderService {
|
||||||
|
void processWeatherUpdateRequest(in RequestInfo request);
|
||||||
|
void processCityNameLookupRequest(in RequestInfo request);
|
||||||
|
void setServiceClient(in IWeatherProviderServiceClient client);
|
||||||
|
void cancelOngoingRequests();
|
||||||
|
void cancelRequest(int requestId);
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weatherservice;
|
||||||
|
|
||||||
|
import lineageos.weather.RequestInfo;
|
||||||
|
import lineageos.weatherservice.ServiceRequestResult;
|
||||||
|
|
||||||
|
interface IWeatherProviderServiceClient {
|
||||||
|
void setServiceRequestState(in RequestInfo requestInfo, in ServiceRequestResult result,
|
||||||
|
int state);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weatherservice;
|
||||||
|
|
||||||
|
parcelable ServiceRequestResult;
|
31
app/src/main/java/lineageos/app/LineageContextConstants.java
Normal file
31
app/src/main/java/lineageos/app/LineageContextConstants.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015, The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.app;
|
||||||
|
|
||||||
|
|
||||||
|
public final class LineageContextConstants {
|
||||||
|
|
||||||
|
private LineageContextConstants() {
|
||||||
|
// Empty constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String LINEAGE_WEATHER_SERVICE = "lineageweather";
|
||||||
|
|
||||||
|
public static class Features {
|
||||||
|
public static final String WEATHER_SERVICES = "org.lineageos.weather";
|
||||||
|
}
|
||||||
|
}
|
32
app/src/main/java/lineageos/os/Build.java
Normal file
32
app/src/main/java/lineageos/os/Build.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.os;
|
||||||
|
|
||||||
|
|
||||||
|
public class Build {
|
||||||
|
public static class LINEAGE_VERSION_CODES {
|
||||||
|
public static final int APRICOT = 1;
|
||||||
|
public static final int BOYSENBERRY = 2;
|
||||||
|
public static final int CANTALOUPE = 3;
|
||||||
|
public static final int DRAGON_FRUIT = 4;
|
||||||
|
public static final int ELDERBERRY = 5;
|
||||||
|
public static final int FIG = 6;
|
||||||
|
public static final int GUAVA = 7;
|
||||||
|
public static final int HACKBERRY = 8;
|
||||||
|
public static final int ILAMA = 9;
|
||||||
|
}
|
||||||
|
}
|
153
app/src/main/java/lineageos/os/Concierge.java
Normal file
153
app/src/main/java/lineageos/os/Concierge.java
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.os;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
import lineageos.os.Build.LINEAGE_VERSION_CODES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply, Concierge handles your parcels and makes sure they get marshalled and unmarshalled
|
||||||
|
* correctly when cross IPC boundaries even when there is a version mismatch between the client
|
||||||
|
* sdk level and the framework implementation.
|
||||||
|
*
|
||||||
|
* <p>On incoming parcel (to be unmarshalled):
|
||||||
|
*
|
||||||
|
* <pre class="prettyprint">
|
||||||
|
* ParcelInfo incomingParcelInfo = Concierge.receiveParcel(incomingParcel);
|
||||||
|
* int parcelableVersion = incomingParcelInfo.getParcelVersion();
|
||||||
|
*
|
||||||
|
* // Do unmarshalling steps here iterating over every plausible version
|
||||||
|
*
|
||||||
|
* // Complete the process
|
||||||
|
* incomingParcelInfo.complete();
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>On outgoing parcel (to be marshalled):
|
||||||
|
*
|
||||||
|
* <pre class="prettyprint">
|
||||||
|
* ParcelInfo outgoingParcelInfo = Concierge.prepareParcel(incomingParcel);
|
||||||
|
*
|
||||||
|
* // Do marshalling steps here iterating over every plausible version
|
||||||
|
*
|
||||||
|
* // Complete the process
|
||||||
|
* outgoingParcelInfo.complete();
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public final class Concierge {
|
||||||
|
|
||||||
|
/** Not instantiable */
|
||||||
|
private Concierge() {
|
||||||
|
// Don't instantiate
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since there might be a case where new versions of the lineage framework use applications running
|
||||||
|
* old versions of the protocol (and thus old versions of this class), we need a versioning
|
||||||
|
* system for the parcels sent between the core framework and its sdk users.
|
||||||
|
*
|
||||||
|
* This parcelable version should be the latest version API version listed in
|
||||||
|
* {@link LINEAGE_VERSION_CODES}
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final int PARCELABLE_VERSION = LINEAGE_VERSION_CODES.ILAMA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the concierge to receive our parcel, so we can get information from it.
|
||||||
|
*
|
||||||
|
* MUST CALL {@link ParcelInfo#complete()} AFTER UNMARSHALLING.
|
||||||
|
*
|
||||||
|
* @param parcel Incoming parcel to be unmarshalled
|
||||||
|
* @return {@link ParcelInfo} containing parcel information, specifically the version.
|
||||||
|
*/
|
||||||
|
public static ParcelInfo receiveParcel(Parcel parcel) {
|
||||||
|
return new ParcelInfo(parcel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare a parcel for the Concierge.
|
||||||
|
*
|
||||||
|
* MUST CALL {@link ParcelInfo#complete()} AFTER MARSHALLING.
|
||||||
|
*
|
||||||
|
* @param parcel Outgoing parcel to be marshalled
|
||||||
|
* @return {@link ParcelInfo} containing parcel information, specifically the version.
|
||||||
|
*/
|
||||||
|
public static ParcelInfo prepareParcel(Parcel parcel) {
|
||||||
|
return new ParcelInfo(parcel, PARCELABLE_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parcel header info specific to the Parcel object that is passed in via
|
||||||
|
* {@link #prepareParcel(Parcel)} or {@link #receiveParcel(Parcel)}. The exposed method
|
||||||
|
* of {@link #getParcelVersion()} gets the api level of the parcel object.
|
||||||
|
*/
|
||||||
|
public final static class ParcelInfo {
|
||||||
|
private Parcel mParcel;
|
||||||
|
private int mParcelableVersion;
|
||||||
|
private int mParcelableSize;
|
||||||
|
private int mStartPosition;
|
||||||
|
private int mSizePosition;
|
||||||
|
private boolean mCreation = false;
|
||||||
|
|
||||||
|
ParcelInfo(Parcel parcel) {
|
||||||
|
mCreation = false;
|
||||||
|
mParcel = parcel;
|
||||||
|
mParcelableVersion = parcel.readInt();
|
||||||
|
mParcelableSize = parcel.readInt();
|
||||||
|
mStartPosition = parcel.dataPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
ParcelInfo(Parcel parcel, int parcelableVersion) {
|
||||||
|
mCreation = true;
|
||||||
|
mParcel = parcel;
|
||||||
|
mParcelableVersion = parcelableVersion;
|
||||||
|
|
||||||
|
// Write parcelable version, make sure to define explicit changes
|
||||||
|
// within {@link #PARCELABLE_VERSION);
|
||||||
|
mParcel.writeInt(mParcelableVersion);
|
||||||
|
|
||||||
|
// Inject a placeholder that will store the parcel size from this point on
|
||||||
|
// (not including the size itself).
|
||||||
|
mSizePosition = parcel.dataPosition();
|
||||||
|
mParcel.writeInt(0);
|
||||||
|
mStartPosition = parcel.dataPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the parcel version from the {@link Parcel} received by the Concierge.
|
||||||
|
* @return {@link #PARCELABLE_VERSION} of the {@link Parcel}
|
||||||
|
*/
|
||||||
|
public int getParcelVersion() {
|
||||||
|
return mParcelableVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete the {@link ParcelInfo} for the Concierge.
|
||||||
|
*/
|
||||||
|
public void complete() {
|
||||||
|
if (mCreation) {
|
||||||
|
// Go back and write size
|
||||||
|
mParcelableSize = mParcel.dataPosition() - mStartPosition;
|
||||||
|
mParcel.setDataPosition(mSizePosition);
|
||||||
|
mParcel.writeInt(mParcelableSize);
|
||||||
|
mParcel.setDataPosition(mStartPosition + mParcelableSize);
|
||||||
|
} else {
|
||||||
|
mParcel.setDataPosition(mStartPosition + mParcelableSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
245
app/src/main/java/lineageos/providers/WeatherContract.java
Normal file
245
app/src/main/java/lineageos/providers/WeatherContract.java
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.providers;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contract between the weather provider and applications.
|
||||||
|
*/
|
||||||
|
public class WeatherContract {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authority of the weather content provider
|
||||||
|
*/
|
||||||
|
public static final String AUTHORITY = "org.lineageos.weather";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A content:// style uri to the authority for the weather provider
|
||||||
|
*/
|
||||||
|
public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
|
||||||
|
|
||||||
|
public static class WeatherColumns {
|
||||||
|
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "weather");
|
||||||
|
|
||||||
|
public static final Uri CURRENT_AND_FORECAST_WEATHER_URI
|
||||||
|
= Uri.withAppendedPath(CONTENT_URI, "current_and_forecast");
|
||||||
|
public static final Uri CURRENT_WEATHER_URI
|
||||||
|
= Uri.withAppendedPath(CONTENT_URI, "current");
|
||||||
|
public static final Uri FORECAST_WEATHER_URI
|
||||||
|
= Uri.withAppendedPath(CONTENT_URI, "forecast");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The city name
|
||||||
|
* <P>Type: TEXT</P>
|
||||||
|
*/
|
||||||
|
public static final String CURRENT_CITY = "city";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Valid {@link WeatherCode}
|
||||||
|
* <P>Type: INTEGER</P>
|
||||||
|
*/
|
||||||
|
public static final String CURRENT_CONDITION_CODE = "condition_code";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A localized string mapped to the current weather condition code. Note that, if no
|
||||||
|
* locale is found, the string will be in english
|
||||||
|
* <P>Type: TEXT</P>
|
||||||
|
*/
|
||||||
|
public static final String CURRENT_CONDITION = "condition";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current weather temperature
|
||||||
|
* <P>Type: DOUBLE</P>
|
||||||
|
*/
|
||||||
|
public static final String CURRENT_TEMPERATURE = "temperature";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unit in which current temperature is reported
|
||||||
|
* <P>Type: INTEGER</P>
|
||||||
|
* Can be one of the following:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link TempUnit#CELSIUS}</li>
|
||||||
|
* <li>{@link TempUnit#FAHRENHEIT}</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public static final String CURRENT_TEMPERATURE_UNIT = "temperature_unit";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current weather humidity
|
||||||
|
* <P>Type: DOUBLE</P>
|
||||||
|
*/
|
||||||
|
public static final String CURRENT_HUMIDITY = "humidity";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current wind direction (in degrees)
|
||||||
|
* <P>Type: DOUBLE</P>
|
||||||
|
*/
|
||||||
|
public static final String CURRENT_WIND_DIRECTION = "wind_direction";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current wind speed
|
||||||
|
* <P>Type: DOUBLE</P>
|
||||||
|
*/
|
||||||
|
public static final String CURRENT_WIND_SPEED = "wind_speed";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unit in which the wind speed is reported
|
||||||
|
* <P>Type: INTEGER</P>
|
||||||
|
* Can be one of the following:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link WindSpeedUnit#KPH}</li>
|
||||||
|
* <li>{@link WindSpeedUnit#MPH}</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public static final String CURRENT_WIND_SPEED_UNIT = "wind_speed_unit";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp when this weather was reported
|
||||||
|
* <P>Type: LONG</P>
|
||||||
|
*/
|
||||||
|
public static final String CURRENT_TIMESTAMP = "timestamp";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Today's high temperature.
|
||||||
|
* <p>Type: DOUBLE</p>
|
||||||
|
*/
|
||||||
|
public static final String TODAYS_HIGH_TEMPERATURE = "todays_high";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Today's low temperature.
|
||||||
|
* <p>Type: DOUBLE</p>
|
||||||
|
*/
|
||||||
|
public static final String TODAYS_LOW_TEMPERATURE = "todays_low";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The forecasted low temperature
|
||||||
|
* <P>Type: DOUBLE</P>
|
||||||
|
*/
|
||||||
|
public static final String FORECAST_LOW = "forecast_low";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The forecasted high temperature
|
||||||
|
* <P>Type: DOUBLE</P>
|
||||||
|
*/
|
||||||
|
public static final String FORECAST_HIGH = "forecast_high";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A localized string mapped to the forecasted weather condition code. Note that, if no
|
||||||
|
* locale is found, the string will be in english
|
||||||
|
* <P>Type: TEXT</P>
|
||||||
|
*/
|
||||||
|
public static final String FORECAST_CONDITION = "forecast_condition";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The code identifying the forecasted weather condition.
|
||||||
|
* @see #CURRENT_CONDITION_CODE
|
||||||
|
*/
|
||||||
|
public static final String FORECAST_CONDITION_CODE = "forecast_condition_code";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temperature units
|
||||||
|
*/
|
||||||
|
public static final class TempUnit {
|
||||||
|
private TempUnit() {}
|
||||||
|
public final static int CELSIUS = 1;
|
||||||
|
public final static int FAHRENHEIT = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wind speed units
|
||||||
|
*/
|
||||||
|
public static final class WindSpeedUnit {
|
||||||
|
private WindSpeedUnit() {}
|
||||||
|
/**
|
||||||
|
* Kilometers per hour
|
||||||
|
*/
|
||||||
|
public final static int KPH = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Miles per hour
|
||||||
|
*/
|
||||||
|
public final static int MPH = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weather condition codes
|
||||||
|
*/
|
||||||
|
public static final class WeatherCode {
|
||||||
|
private WeatherCode() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public final static int WEATHER_CODE_MIN = 0;
|
||||||
|
|
||||||
|
public final static int TORNADO = 0;
|
||||||
|
public final static int TROPICAL_STORM = 1;
|
||||||
|
public final static int HURRICANE = 2;
|
||||||
|
public final static int SEVERE_THUNDERSTORMS = 3;
|
||||||
|
public final static int THUNDERSTORMS = 4;
|
||||||
|
public final static int MIXED_RAIN_AND_SNOW = 5;
|
||||||
|
public final static int MIXED_RAIN_AND_SLEET = 6;
|
||||||
|
public final static int MIXED_SNOW_AND_SLEET = 7;
|
||||||
|
public final static int FREEZING_DRIZZLE = 8;
|
||||||
|
public final static int DRIZZLE = 9;
|
||||||
|
public final static int FREEZING_RAIN = 10;
|
||||||
|
public final static int SHOWERS = 11;
|
||||||
|
public final static int SNOW_FLURRIES = 12;
|
||||||
|
public final static int LIGHT_SNOW_SHOWERS = 13;
|
||||||
|
public final static int BLOWING_SNOW = 14;
|
||||||
|
public final static int SNOW = 15;
|
||||||
|
public final static int HAIL = 16;
|
||||||
|
public final static int SLEET = 17;
|
||||||
|
public final static int DUST = 18;
|
||||||
|
public final static int FOGGY = 19;
|
||||||
|
public final static int HAZE = 20;
|
||||||
|
public final static int SMOKY = 21;
|
||||||
|
public final static int BLUSTERY = 22;
|
||||||
|
public final static int WINDY = 23;
|
||||||
|
public final static int COLD = 24;
|
||||||
|
public final static int CLOUDY = 25;
|
||||||
|
public final static int MOSTLY_CLOUDY_NIGHT = 26;
|
||||||
|
public final static int MOSTLY_CLOUDY_DAY = 27;
|
||||||
|
public final static int PARTLY_CLOUDY_NIGHT = 28;
|
||||||
|
public final static int PARTLY_CLOUDY_DAY = 29;
|
||||||
|
public final static int CLEAR_NIGHT = 30;
|
||||||
|
public final static int SUNNY = 31;
|
||||||
|
public final static int FAIR_NIGHT = 32;
|
||||||
|
public final static int FAIR_DAY = 33;
|
||||||
|
public final static int MIXED_RAIN_AND_HAIL = 34;
|
||||||
|
public final static int HOT = 35;
|
||||||
|
public final static int ISOLATED_THUNDERSTORMS = 36;
|
||||||
|
public final static int SCATTERED_THUNDERSTORMS = 37;
|
||||||
|
public final static int SCATTERED_SHOWERS = 38;
|
||||||
|
public final static int HEAVY_SNOW = 39;
|
||||||
|
public final static int SCATTERED_SNOW_SHOWERS = 40;
|
||||||
|
public final static int PARTLY_CLOUDY = 41;
|
||||||
|
public final static int THUNDERSHOWER = 42;
|
||||||
|
public final static int SNOW_SHOWERS = 43;
|
||||||
|
public final static int ISOLATED_THUNDERSHOWERS = 44;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public final static int WEATHER_CODE_MAX = 44;
|
||||||
|
|
||||||
|
public final static int NOT_AVAILABLE = 3200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
435
app/src/main/java/lineageos/weather/LineageWeatherManager.java
Normal file
435
app/src/main/java/lineageos/weather/LineageWeatherManager.java
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weather;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import lineageos.app.LineageContextConstants;
|
||||||
|
import lineageos.providers.WeatherContract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides access to the weather services in the device.
|
||||||
|
*/
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||||
|
public class LineageWeatherManager {
|
||||||
|
|
||||||
|
private static ILineageWeatherManager sWeatherManagerService;
|
||||||
|
private static LineageWeatherManager sInstance;
|
||||||
|
private Context mContext;
|
||||||
|
private Map<RequestInfo,WeatherUpdateRequestListener> mWeatherUpdateRequestListeners
|
||||||
|
= Collections.synchronizedMap(new HashMap<RequestInfo,WeatherUpdateRequestListener>());
|
||||||
|
private Map<RequestInfo,LookupCityRequestListener> mLookupNameRequestListeners
|
||||||
|
= Collections.synchronizedMap(new HashMap<RequestInfo,LookupCityRequestListener>());
|
||||||
|
private Handler mHandler;
|
||||||
|
private Set<WeatherServiceProviderChangeListener> mProviderChangedListeners = new ArraySet<>();
|
||||||
|
|
||||||
|
private static final String TAG = LineageWeatherManager.class.getSimpleName();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The different request statuses
|
||||||
|
*/
|
||||||
|
public static final class RequestStatus {
|
||||||
|
|
||||||
|
private RequestStatus() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request successfully completed
|
||||||
|
*/
|
||||||
|
public static final int COMPLETED = 1;
|
||||||
|
/**
|
||||||
|
* An error occurred while trying to honor the request
|
||||||
|
*/
|
||||||
|
public static final int FAILED = -1;
|
||||||
|
/**
|
||||||
|
* The request can't be processed at this time
|
||||||
|
*/
|
||||||
|
public static final int SUBMITTED_TOO_SOON = -2;
|
||||||
|
/**
|
||||||
|
* Another request is already in progress
|
||||||
|
*/
|
||||||
|
public static final int ALREADY_IN_PROGRESS = -3;
|
||||||
|
/**
|
||||||
|
* No match found for the query
|
||||||
|
*/
|
||||||
|
public static final int NO_MATCH_FOUND = -4;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LineageWeatherManager(Context context) {
|
||||||
|
Context appContext = context.getApplicationContext();
|
||||||
|
mContext = (appContext != null) ? appContext : context;
|
||||||
|
sWeatherManagerService = getService();
|
||||||
|
|
||||||
|
if (context.getPackageManager().hasSystemFeature(
|
||||||
|
LineageContextConstants.Features.WEATHER_SERVICES) && (sWeatherManagerService == null)) {
|
||||||
|
Log.wtf(TAG, "Unable to bind the LineageWeatherManagerService");
|
||||||
|
}
|
||||||
|
mHandler = new Handler(appContext.getMainLooper());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or creates an instance of the {@link lineageos.weather.LineageWeatherManager}
|
||||||
|
* @param context
|
||||||
|
* @return {@link LineageWeatherManager}
|
||||||
|
*/
|
||||||
|
public static LineageWeatherManager getInstance(Context context) {
|
||||||
|
if (sInstance == null) {
|
||||||
|
sInstance = new LineageWeatherManager(context);
|
||||||
|
}
|
||||||
|
return sInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@SuppressLint("PrivateApi")
|
||||||
|
public static ILineageWeatherManager getService() {
|
||||||
|
if (sWeatherManagerService != null) {
|
||||||
|
return sWeatherManagerService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a Gadgetbridge hack
|
||||||
|
IBinder binder = null;
|
||||||
|
try {
|
||||||
|
Class localClass = Class.forName("android.os.ServiceManager");
|
||||||
|
Method getService = localClass.getMethod("getService", String.class);
|
||||||
|
Object result = getService.invoke(localClass, LineageContextConstants.LINEAGE_WEATHER_SERVICE);
|
||||||
|
if (result != null) {
|
||||||
|
binder = (IBinder) result;
|
||||||
|
}
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (binder != null) {
|
||||||
|
sWeatherManagerService = ILineageWeatherManager.Stub.asInterface(binder);
|
||||||
|
return sWeatherManagerService;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the weather service to request the latest available weather information for
|
||||||
|
* the supplied {@link android.location.Location} location.
|
||||||
|
*
|
||||||
|
* @param location The location you want to get the latest weather data from.
|
||||||
|
* @param listener {@link WeatherUpdateRequestListener} To be notified once the active weather
|
||||||
|
* service provider has finished
|
||||||
|
* processing your request
|
||||||
|
* @return An integer that identifies the request submitted to the weather service
|
||||||
|
* Note that this method might return -1 if an error occurred while trying to submit
|
||||||
|
* the request.
|
||||||
|
*/
|
||||||
|
public int requestWeatherUpdate(@NonNull Location location,
|
||||||
|
@NonNull WeatherUpdateRequestListener listener) {
|
||||||
|
if (sWeatherManagerService == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
int tempUnit = WeatherContract.WeatherColumns.TempUnit.CELSIUS;
|
||||||
|
|
||||||
|
RequestInfo info = new RequestInfo
|
||||||
|
.Builder(mRequestInfoListener)
|
||||||
|
.setLocation(location)
|
||||||
|
.setTemperatureUnit(tempUnit)
|
||||||
|
.build();
|
||||||
|
if (listener != null) mWeatherUpdateRequestListeners.put(info, listener);
|
||||||
|
sWeatherManagerService.updateWeather(info);
|
||||||
|
return info.hashCode();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the weather service to request the latest weather information for the provided
|
||||||
|
* WeatherLocation. This is the preferred method for requesting a weather update.
|
||||||
|
*
|
||||||
|
* @param weatherLocation A {@link lineageos.weather.WeatherLocation} that was previously
|
||||||
|
* obtained by calling
|
||||||
|
* {@link #lookupCity(String, LookupCityRequestListener)}
|
||||||
|
* @param listener {@link WeatherUpdateRequestListener} To be notified once the active weather
|
||||||
|
* service provider has finished
|
||||||
|
* processing your request
|
||||||
|
* @return An integer that identifies the request submitted to the weather service.
|
||||||
|
* Note that this method might return -1 if an error occurred while trying to submit
|
||||||
|
* the request.
|
||||||
|
*/
|
||||||
|
public int requestWeatherUpdate(@NonNull WeatherLocation weatherLocation,
|
||||||
|
@NonNull WeatherUpdateRequestListener listener) {
|
||||||
|
if (sWeatherManagerService == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
int tempUnit = WeatherContract.WeatherColumns.TempUnit.CELSIUS;
|
||||||
|
|
||||||
|
RequestInfo info = new RequestInfo
|
||||||
|
.Builder(mRequestInfoListener)
|
||||||
|
.setWeatherLocation(weatherLocation)
|
||||||
|
.setTemperatureUnit(tempUnit)
|
||||||
|
.build();
|
||||||
|
if (listener != null) mWeatherUpdateRequestListeners.put(info, listener);
|
||||||
|
sWeatherManagerService.updateWeather(info);
|
||||||
|
return info.hashCode();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the active weather provider service to lookup the supplied city name.
|
||||||
|
*
|
||||||
|
* @param city The city name
|
||||||
|
* @param listener {@link LookupCityRequestListener} To be notified once the request has been
|
||||||
|
* completed. Upon success, a list of
|
||||||
|
* {@link lineageos.weather.WeatherLocation}
|
||||||
|
* will be provided
|
||||||
|
* @return An integer that identifies the request submitted to the weather service.
|
||||||
|
* Note that this method might return -1 if an error occurred while trying to submit
|
||||||
|
* the request.
|
||||||
|
*/
|
||||||
|
public int lookupCity(@NonNull String city, @NonNull LookupCityRequestListener listener) {
|
||||||
|
if (sWeatherManagerService == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
RequestInfo info = new RequestInfo
|
||||||
|
.Builder(mRequestInfoListener)
|
||||||
|
.setCityName(city)
|
||||||
|
.build();
|
||||||
|
if (listener != null) mLookupNameRequestListeners.put(info, listener);
|
||||||
|
sWeatherManagerService.lookupCity(info);
|
||||||
|
return info.hashCode();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels a request that was previously submitted to the weather service.
|
||||||
|
* @param requestId The ID that you received when the request was submitted
|
||||||
|
*/
|
||||||
|
public void cancelRequest(int requestId) {
|
||||||
|
if (sWeatherManagerService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
sWeatherManagerService.cancelRequest(requestId);
|
||||||
|
}catch (RemoteException e){
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a {@link WeatherServiceProviderChangeListener} to be notified when a new weather
|
||||||
|
* service provider becomes active.
|
||||||
|
* @param listener {@link WeatherServiceProviderChangeListener} to register
|
||||||
|
*/
|
||||||
|
public void registerWeatherServiceProviderChangeListener(
|
||||||
|
@NonNull WeatherServiceProviderChangeListener listener) {
|
||||||
|
if (sWeatherManagerService == null) return;
|
||||||
|
|
||||||
|
synchronized (mProviderChangedListeners) {
|
||||||
|
if (mProviderChangedListeners.contains(listener)) {
|
||||||
|
throw new IllegalArgumentException("Listener already registered");
|
||||||
|
}
|
||||||
|
if (mProviderChangedListeners.size() == 0) {
|
||||||
|
try {
|
||||||
|
sWeatherManagerService.registerWeatherServiceProviderChangeListener(
|
||||||
|
mProviderChangeListener);
|
||||||
|
} catch (RemoteException e){
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mProviderChangedListeners.add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters a listener
|
||||||
|
* @param listener A previously registered {@link WeatherServiceProviderChangeListener}
|
||||||
|
*/
|
||||||
|
public void unregisterWeatherServiceProviderChangeListener(
|
||||||
|
@NonNull WeatherServiceProviderChangeListener listener) {
|
||||||
|
if (sWeatherManagerService == null) return;
|
||||||
|
|
||||||
|
synchronized (mProviderChangedListeners) {
|
||||||
|
if (!mProviderChangedListeners.contains(listener)) {
|
||||||
|
throw new IllegalArgumentException("Listener was never registered");
|
||||||
|
}
|
||||||
|
mProviderChangedListeners.remove(listener);
|
||||||
|
if (mProviderChangedListeners.size() == 0) {
|
||||||
|
try {
|
||||||
|
sWeatherManagerService.unregisterWeatherServiceProviderChangeListener(
|
||||||
|
mProviderChangeListener);
|
||||||
|
} catch(RemoteException e){
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the service's label as declared by the active weather service provider in its manifest
|
||||||
|
* @return the service's label
|
||||||
|
*/
|
||||||
|
public String getActiveWeatherServiceProviderLabel() {
|
||||||
|
if (sWeatherManagerService == null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return sWeatherManagerService.getActiveWeatherServiceProviderLabel();
|
||||||
|
} catch(RemoteException e){
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final IWeatherServiceProviderChangeListener mProviderChangeListener =
|
||||||
|
new IWeatherServiceProviderChangeListener.Stub() {
|
||||||
|
@Override
|
||||||
|
public void onWeatherServiceProviderChanged(final String providerName) {
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
synchronized (mProviderChangedListeners) {
|
||||||
|
List<WeatherServiceProviderChangeListener> deadListeners
|
||||||
|
= new ArrayList<>();
|
||||||
|
for (WeatherServiceProviderChangeListener listener
|
||||||
|
: mProviderChangedListeners) {
|
||||||
|
try {
|
||||||
|
listener.onWeatherServiceProviderChanged(providerName);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
deadListeners.add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (deadListeners.size() > 0) {
|
||||||
|
for (WeatherServiceProviderChangeListener listener : deadListeners) {
|
||||||
|
mProviderChangedListeners.remove(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final IRequestInfoListener mRequestInfoListener = new IRequestInfoListener.Stub() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWeatherRequestCompleted(final RequestInfo requestInfo, final int status,
|
||||||
|
final WeatherInfo weatherInfo) {
|
||||||
|
final WeatherUpdateRequestListener listener
|
||||||
|
= mWeatherUpdateRequestListeners.remove(requestInfo);
|
||||||
|
if (listener != null) {
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
listener.onWeatherRequestCompleted(status, weatherInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLookupCityRequestCompleted(RequestInfo requestInfo, final int status,
|
||||||
|
final List<WeatherLocation> weatherLocations) {
|
||||||
|
|
||||||
|
final LookupCityRequestListener listener
|
||||||
|
= mLookupNameRequestListeners.remove(requestInfo);
|
||||||
|
if (listener != null) {
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
listener.onLookupCityRequestCompleted(status, weatherLocations);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface used to receive notifications upon completion of a weather update request
|
||||||
|
*/
|
||||||
|
public interface WeatherUpdateRequestListener {
|
||||||
|
/**
|
||||||
|
* This method will be called when the weather service provider has finished processing the
|
||||||
|
* request
|
||||||
|
*
|
||||||
|
* @param status See {@link RequestStatus}
|
||||||
|
*
|
||||||
|
* @param weatherInfo A fully populated {@link WeatherInfo} if state is
|
||||||
|
* {@link RequestStatus#COMPLETED}, null otherwise
|
||||||
|
*/
|
||||||
|
void onWeatherRequestCompleted(int status, WeatherInfo weatherInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface used to receive notifications upon completion of a request to lookup a city name
|
||||||
|
*/
|
||||||
|
public interface LookupCityRequestListener {
|
||||||
|
/**
|
||||||
|
* This method will be called when the weather service provider has finished processing the
|
||||||
|
* request.
|
||||||
|
*
|
||||||
|
* @param status See {@link RequestStatus}
|
||||||
|
*
|
||||||
|
* @param locations A list of {@link WeatherLocation} if the status is
|
||||||
|
* {@link RequestStatus#COMPLETED}, null otherwise
|
||||||
|
*/
|
||||||
|
void onLookupCityRequestCompleted(int status, List<WeatherLocation> locations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface used to be notified when the user changes the weather service provider
|
||||||
|
*/
|
||||||
|
public interface WeatherServiceProviderChangeListener {
|
||||||
|
/**
|
||||||
|
* This method will be called when a new weather service provider becomes active in the
|
||||||
|
* system. The parameter can be null when
|
||||||
|
* <p>The user removed the active weather service provider from the system </p>
|
||||||
|
* <p>The active weather provider was disabled.</p>
|
||||||
|
*
|
||||||
|
* @param providerLabel The label as declared on the weather service provider manifest
|
||||||
|
*/
|
||||||
|
void onWeatherServiceProviderChanged(String providerLabel);
|
||||||
|
}
|
||||||
|
}
|
379
app/src/main/java/lineageos/weather/RequestInfo.java
Normal file
379
app/src/main/java/lineageos/weather/RequestInfo.java
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weather;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import lineageos.os.Build;
|
||||||
|
import lineageos.os.Concierge;
|
||||||
|
import lineageos.os.Concierge.ParcelInfo;
|
||||||
|
import lineageos.providers.WeatherContract;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class holds the information of a request submitted to the active weather provider service
|
||||||
|
*/
|
||||||
|
public final class RequestInfo implements Parcelable {
|
||||||
|
|
||||||
|
private Location mLocation;
|
||||||
|
private String mCityName;
|
||||||
|
private WeatherLocation mWeatherLocation;
|
||||||
|
private int mRequestType;
|
||||||
|
private IRequestInfoListener mListener;
|
||||||
|
private int mTempUnit;
|
||||||
|
private String mKey;
|
||||||
|
private boolean mIsQueryOnly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A request to update the weather data using a geographical {@link android.location.Location}
|
||||||
|
*/
|
||||||
|
public static final int TYPE_WEATHER_BY_GEO_LOCATION_REQ = 1;
|
||||||
|
/**
|
||||||
|
* A request to update the weather data using a {@link WeatherLocation}
|
||||||
|
*/
|
||||||
|
public static final int TYPE_WEATHER_BY_WEATHER_LOCATION_REQ = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A request to look up a city name
|
||||||
|
*/
|
||||||
|
public static final int TYPE_LOOKUP_CITY_NAME_REQ = 3;
|
||||||
|
|
||||||
|
private RequestInfo() {}
|
||||||
|
|
||||||
|
/* package */ static class Builder {
|
||||||
|
private Location mLocation;
|
||||||
|
private String mCityName;
|
||||||
|
private WeatherLocation mWeatherLocation;
|
||||||
|
private int mRequestType;
|
||||||
|
private IRequestInfoListener mListener;
|
||||||
|
private int mTempUnit = WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT;
|
||||||
|
private boolean mIsQueryOnly = false;
|
||||||
|
|
||||||
|
public Builder(IRequestInfoListener listener) {
|
||||||
|
this.mListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the city name and identifies this request as a {@link #TYPE_LOOKUP_CITY_NAME_REQ}
|
||||||
|
* request. If set, will null out the location and weather location. Attempting to set
|
||||||
|
* a null city name will get you an IllegalArgumentException
|
||||||
|
*/
|
||||||
|
public Builder setCityName(String cityName) {
|
||||||
|
if (cityName == null) {
|
||||||
|
throw new IllegalArgumentException("City name can't be null");
|
||||||
|
}
|
||||||
|
this.mCityName = cityName;
|
||||||
|
this.mRequestType = TYPE_LOOKUP_CITY_NAME_REQ;
|
||||||
|
this.mLocation = null;
|
||||||
|
this.mWeatherLocation = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Location and identifies this request as a
|
||||||
|
* {@link #TYPE_WEATHER_BY_GEO_LOCATION_REQ}. If set, will null out the city name and
|
||||||
|
* weather location. Attempting to set a null location will get you an
|
||||||
|
* IllegalArgumentException
|
||||||
|
*/
|
||||||
|
public Builder setLocation(Location location) {
|
||||||
|
if (location == null) {
|
||||||
|
throw new IllegalArgumentException("Location can't be null");
|
||||||
|
}
|
||||||
|
this.mLocation = new Location(location);
|
||||||
|
this.mCityName = null;
|
||||||
|
this.mWeatherLocation = null;
|
||||||
|
this.mRequestType = TYPE_WEATHER_BY_GEO_LOCATION_REQ;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the weather location and identifies this request as a
|
||||||
|
* {@link #TYPE_WEATHER_BY_WEATHER_LOCATION_REQ}. If set, will null out the location and
|
||||||
|
* city name. Attempting to set a null weather location will get you an
|
||||||
|
* IllegalArgumentException
|
||||||
|
*/
|
||||||
|
public Builder setWeatherLocation(WeatherLocation weatherLocation) {
|
||||||
|
if (weatherLocation == null) {
|
||||||
|
throw new IllegalArgumentException("WeatherLocation can't be null");
|
||||||
|
}
|
||||||
|
this.mWeatherLocation = weatherLocation;
|
||||||
|
this.mLocation = null;
|
||||||
|
this.mCityName = null;
|
||||||
|
this.mRequestType = TYPE_WEATHER_BY_WEATHER_LOCATION_REQ;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the unit in which the temperature will be reported if the request is honored.
|
||||||
|
* Valid values are:
|
||||||
|
* <ul>
|
||||||
|
* {@link lineageos.providers.WeatherContract.WeatherColumns.TempUnit#CELSIUS}
|
||||||
|
* {@link lineageos.providers.WeatherContract.WeatherColumns.TempUnit#FAHRENHEIT}
|
||||||
|
* </ul>
|
||||||
|
* Any other value will generate an IllegalArgumentException. If the temperature unit is not
|
||||||
|
* set, the default will be degrees Fahrenheit
|
||||||
|
* @param unit A valid temperature unit
|
||||||
|
*/
|
||||||
|
public Builder setTemperatureUnit(int unit) {
|
||||||
|
if (!isValidTempUnit(unit)) {
|
||||||
|
throw new IllegalArgumentException("Invalid temperature unit");
|
||||||
|
}
|
||||||
|
this.mTempUnit = unit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is a weather request, marks the request as a query only, meaning that the
|
||||||
|
* content provider won't be updated after the active weather service has finished
|
||||||
|
* processing the request.
|
||||||
|
*/
|
||||||
|
public Builder queryOnly() {
|
||||||
|
switch (mRequestType) {
|
||||||
|
case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||||
|
case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||||
|
this.mIsQueryOnly = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.mIsQueryOnly = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine all of the options that have been set and return a new {@link RequestInfo} object
|
||||||
|
* @return {@link RequestInfo}
|
||||||
|
*/
|
||||||
|
public RequestInfo build() {
|
||||||
|
RequestInfo info = new RequestInfo();
|
||||||
|
info.mListener = this.mListener;
|
||||||
|
info.mRequestType = this.mRequestType;
|
||||||
|
info.mCityName = this.mCityName;
|
||||||
|
info.mWeatherLocation = this.mWeatherLocation;
|
||||||
|
info.mLocation = this.mLocation;
|
||||||
|
info.mTempUnit = this.mTempUnit;
|
||||||
|
info.mIsQueryOnly = this.mIsQueryOnly;
|
||||||
|
info.mKey = UUID.randomUUID().toString();
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidTempUnit(int unit) {
|
||||||
|
switch (unit) {
|
||||||
|
case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
|
||||||
|
case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestInfo(Parcel parcel) {
|
||||||
|
// Read parcelable version via the Concierge
|
||||||
|
ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
|
||||||
|
int parcelableVersion = parcelInfo.getParcelVersion();
|
||||||
|
|
||||||
|
if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
|
||||||
|
mKey = parcel.readString();
|
||||||
|
mRequestType = parcel.readInt();
|
||||||
|
switch (mRequestType) {
|
||||||
|
case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||||
|
mLocation = Location.CREATOR.createFromParcel(parcel);
|
||||||
|
mTempUnit = parcel.readInt();
|
||||||
|
break;
|
||||||
|
case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||||
|
mWeatherLocation = WeatherLocation.CREATOR.createFromParcel(parcel);
|
||||||
|
mTempUnit = parcel.readInt();
|
||||||
|
break;
|
||||||
|
case TYPE_LOOKUP_CITY_NAME_REQ:
|
||||||
|
mCityName = parcel.readString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mIsQueryOnly = (parcel.readInt() == 1);
|
||||||
|
mListener = IRequestInfoListener.Stub.asInterface(parcel.readStrongBinder());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete parcel info for the concierge
|
||||||
|
parcelInfo.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The request type
|
||||||
|
*/
|
||||||
|
public int getRequestType() {
|
||||||
|
return mRequestType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link android.location.Location} if this is a request by location, null
|
||||||
|
* otherwise
|
||||||
|
*/
|
||||||
|
public Location getLocation() {
|
||||||
|
return new Location(mLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link lineageos.weather.WeatherLocation} if this is a request by weather
|
||||||
|
* location, null otherwise
|
||||||
|
*/
|
||||||
|
public WeatherLocation getWeatherLocation() {
|
||||||
|
return mWeatherLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public IRequestInfoListener getRequestListener() {
|
||||||
|
return mListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the city name if this is a lookup request, null otherwise
|
||||||
|
*/
|
||||||
|
public String getCityName() {
|
||||||
|
return mCityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the temperature unit if this is a weather request, -1 otherwise
|
||||||
|
*/
|
||||||
|
public int getTemperatureUnit() {
|
||||||
|
switch (mRequestType) {
|
||||||
|
case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||||
|
case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||||
|
return mTempUnit;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if this is a weather request, whether the request will update the content provider.
|
||||||
|
* False for other kind of requests
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public boolean isQueryOnlyWeatherRequest() {
|
||||||
|
switch (mRequestType) {
|
||||||
|
case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||||
|
case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||||
|
return mIsQueryOnly;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<RequestInfo> CREATOR = new Creator<RequestInfo>() {
|
||||||
|
@Override
|
||||||
|
public RequestInfo createFromParcel(Parcel in) {
|
||||||
|
return new RequestInfo(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestInfo[] newArray(int size) {
|
||||||
|
return new RequestInfo[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
// Tell the concierge to prepare the parcel
|
||||||
|
ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
|
||||||
|
|
||||||
|
// ==== ELDERBERRY =====
|
||||||
|
dest.writeString(mKey);
|
||||||
|
dest.writeInt(mRequestType);
|
||||||
|
switch (mRequestType) {
|
||||||
|
case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||||
|
mLocation.writeToParcel(dest, 0);
|
||||||
|
dest.writeInt(mTempUnit);
|
||||||
|
break;
|
||||||
|
case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||||
|
mWeatherLocation.writeToParcel(dest, 0);
|
||||||
|
dest.writeInt(mTempUnit);
|
||||||
|
break;
|
||||||
|
case TYPE_LOOKUP_CITY_NAME_REQ:
|
||||||
|
dest.writeString(mCityName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
dest.writeInt(mIsQueryOnly == true ? 1 : 0);
|
||||||
|
dest.writeStrongBinder(mListener.asBinder());
|
||||||
|
|
||||||
|
// Complete parcel info for the concierge
|
||||||
|
parcelInfo.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("{ Request for ");
|
||||||
|
switch (mRequestType) {
|
||||||
|
case TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||||
|
builder.append("Location: ").append(mLocation);
|
||||||
|
builder.append(" Temp Unit: ");
|
||||||
|
if (mTempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
|
||||||
|
builder.append("Fahrenheit");
|
||||||
|
} else {
|
||||||
|
builder.append(" Celsius");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||||
|
builder.append("WeatherLocation: ").append(mWeatherLocation);
|
||||||
|
builder.append(" Temp Unit: ");
|
||||||
|
if (mTempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
|
||||||
|
builder.append("Fahrenheit");
|
||||||
|
} else {
|
||||||
|
builder.append(" Celsius");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_LOOKUP_CITY_NAME_REQ:
|
||||||
|
builder.append("Lookup City: ").append(mCityName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return builder.append(" }").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) return false;
|
||||||
|
|
||||||
|
if (getClass() == obj.getClass()) {
|
||||||
|
RequestInfo info = (RequestInfo) obj;
|
||||||
|
return (TextUtils.equals(mKey, info.mKey));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
642
app/src/main/java/lineageos/weather/WeatherInfo.java
Executable file
642
app/src/main/java/lineageos/weather/WeatherInfo.java
Executable file
@ -0,0 +1,642 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanongenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weather;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import lineageos.os.Build;
|
||||||
|
import lineageos.os.Concierge;
|
||||||
|
import lineageos.os.Concierge.ParcelInfo;
|
||||||
|
import lineageos.providers.WeatherContract;
|
||||||
|
import lineageos.weatherservice.ServiceRequest;
|
||||||
|
import lineageos.weatherservice.ServiceRequestResult;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents the weather information that a
|
||||||
|
* {@link lineageos.weatherservice.WeatherProviderService} will use to update the weather content
|
||||||
|
* provider. A weather provider service will be called by the system to process an update
|
||||||
|
* request at any time. If the service successfully processes the request, then the weather provider
|
||||||
|
* service is responsible of calling
|
||||||
|
* {@link ServiceRequest#complete(ServiceRequestResult)} to notify the
|
||||||
|
* system that the request was completed and that the weather content provider should be updated
|
||||||
|
* with the supplied weather information.
|
||||||
|
*/
|
||||||
|
public final class WeatherInfo implements Parcelable {
|
||||||
|
|
||||||
|
private String mCity;
|
||||||
|
private int mConditionCode;
|
||||||
|
private double mTemperature;
|
||||||
|
private int mTempUnit;
|
||||||
|
private double mTodaysHighTemp;
|
||||||
|
private double mTodaysLowTemp;
|
||||||
|
private double mHumidity;
|
||||||
|
private double mWindSpeed;
|
||||||
|
private double mWindDirection;
|
||||||
|
private int mWindSpeedUnit;
|
||||||
|
private long mTimestamp;
|
||||||
|
private List<DayForecast> mForecastList;
|
||||||
|
private String mKey;
|
||||||
|
|
||||||
|
private WeatherInfo() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder class for {@link WeatherInfo}
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private String mCity;
|
||||||
|
private int mConditionCode = WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE;
|
||||||
|
private double mTemperature;
|
||||||
|
private int mTempUnit;
|
||||||
|
private double mTodaysHighTemp = Double.NaN;
|
||||||
|
private double mTodaysLowTemp = Double.NaN;
|
||||||
|
private double mHumidity = Double.NaN;
|
||||||
|
private double mWindSpeed = Double.NaN;
|
||||||
|
private double mWindDirection = Double.NaN;
|
||||||
|
private int mWindSpeedUnit = WeatherContract.WeatherColumns.WindSpeedUnit.MPH;
|
||||||
|
private long mTimestamp = -1;
|
||||||
|
private List<DayForecast> mForecastList = new ArrayList<>(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cityName A valid city name. Attempting to pass null will get you an
|
||||||
|
* IllegalArgumentException
|
||||||
|
* @param temperature A valid temperature value. Attempting pass an invalid double value,
|
||||||
|
* will get you an IllegalArgumentException
|
||||||
|
* @param tempUnit A valid temperature unit value. See
|
||||||
|
* {@link lineageos.providers.WeatherContract.WeatherColumns.TempUnit} for
|
||||||
|
* valid values. Attempting to pass an invalid temperature unit will get you
|
||||||
|
* an IllegalArgumentException
|
||||||
|
*/
|
||||||
|
public Builder(@NonNull String cityName, double temperature, int tempUnit) {
|
||||||
|
if (cityName == null) {
|
||||||
|
throw new IllegalArgumentException("City name can't be null");
|
||||||
|
}
|
||||||
|
if (Double.isNaN(temperature)) {
|
||||||
|
throw new IllegalArgumentException("Invalid temperature");
|
||||||
|
}
|
||||||
|
if (!isValidTempUnit(tempUnit)) {
|
||||||
|
throw new IllegalArgumentException("Invalid temperature unit");
|
||||||
|
}
|
||||||
|
this.mCity = cityName;
|
||||||
|
this.mTemperature = temperature;
|
||||||
|
this.mTempUnit = tempUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param timeStamp A timestamp indicating when this data was generated. If timestamps is
|
||||||
|
* not set, then the builder will set it to the time of object creation
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder setTimestamp(long timeStamp) {
|
||||||
|
mTimestamp = timeStamp;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param humidity The weather humidity. Attempting to pass an invalid double value will get
|
||||||
|
* you an IllegalArgumentException
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder setHumidity(double humidity) {
|
||||||
|
if (Double.isNaN(humidity)) {
|
||||||
|
throw new IllegalArgumentException("Invalid humidity value");
|
||||||
|
}
|
||||||
|
|
||||||
|
mHumidity = humidity;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param windSpeed The wind speed. Attempting to pass an invalid double value will get you
|
||||||
|
* an IllegalArgumentException
|
||||||
|
* @param windDirection The wind direction. Attempting to pass an invalid double value will
|
||||||
|
* get you an IllegalArgumentException
|
||||||
|
* @param windSpeedUnit A valid wind speed direction unit. See
|
||||||
|
* {@link lineageos.providers.WeatherContract.WeatherColumns.WindSpeedUnit}
|
||||||
|
* for valid values. Attempting to pass an invalid speed unit will get
|
||||||
|
* you an IllegalArgumentException
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder setWind(double windSpeed, double windDirection, int windSpeedUnit) {
|
||||||
|
if (Double.isNaN(windSpeed)) {
|
||||||
|
throw new IllegalArgumentException("Invalid wind speed value");
|
||||||
|
}
|
||||||
|
if (Double.isNaN(windDirection)) {
|
||||||
|
throw new IllegalArgumentException("Invalid wind direction value");
|
||||||
|
}
|
||||||
|
if (!isValidWindSpeedUnit(windSpeedUnit)) {
|
||||||
|
throw new IllegalArgumentException("Invalid speed unit");
|
||||||
|
}
|
||||||
|
mWindSpeed = windSpeed;
|
||||||
|
mWindSpeedUnit = windSpeedUnit;
|
||||||
|
mWindDirection = windDirection;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param conditionCode A valid weather condition code. See
|
||||||
|
* {@link lineageos.providers.WeatherContract.WeatherColumns.WeatherCode}
|
||||||
|
* for valid codes. Attempting to pass an invalid code will get you an
|
||||||
|
* IllegalArgumentException.
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder setWeatherCondition(int conditionCode) {
|
||||||
|
if (!isValidWeatherCode(conditionCode)) {
|
||||||
|
throw new IllegalArgumentException("Invalid weather condition code");
|
||||||
|
}
|
||||||
|
mConditionCode = conditionCode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param forecasts A valid array list of {@link DayForecast} objects. Attempting to pass
|
||||||
|
* null will get you an IllegalArgumentException'
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder setForecast(@NonNull List<DayForecast> forecasts) {
|
||||||
|
if (forecasts == null) {
|
||||||
|
throw new IllegalArgumentException("Forecast list can't be null");
|
||||||
|
}
|
||||||
|
mForecastList = forecasts;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param todaysHigh Today's high temperature. Attempting to pass an invalid double value
|
||||||
|
* will get you an IllegalArgumentException
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder setTodaysHigh(double todaysHigh) {
|
||||||
|
if (Double.isNaN(todaysHigh)) {
|
||||||
|
throw new IllegalArgumentException("Invalid temperature value");
|
||||||
|
}
|
||||||
|
mTodaysHighTemp = todaysHigh;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param todaysLow Today's low temperature. Attempting to pass an invalid double value will
|
||||||
|
* get you an IllegalArgumentException
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Builder setTodaysLow(double todaysLow) {
|
||||||
|
if (Double.isNaN(todaysLow)) {
|
||||||
|
throw new IllegalArgumentException("Invalid temperature value");
|
||||||
|
}
|
||||||
|
mTodaysLowTemp = todaysLow;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine all of the options that have been set and return a new {@link WeatherInfo} object
|
||||||
|
* @return {@link WeatherInfo}
|
||||||
|
*/
|
||||||
|
public WeatherInfo build() {
|
||||||
|
WeatherInfo info = new WeatherInfo();
|
||||||
|
info.mCity = this.mCity;
|
||||||
|
info.mConditionCode = this.mConditionCode;
|
||||||
|
info.mTemperature = this.mTemperature;
|
||||||
|
info.mTempUnit = this.mTempUnit;
|
||||||
|
info.mHumidity = this.mHumidity;
|
||||||
|
info.mWindSpeed = this.mWindSpeed;
|
||||||
|
info.mWindDirection = this.mWindDirection;
|
||||||
|
info.mWindSpeedUnit = this.mWindSpeedUnit;
|
||||||
|
info.mTimestamp = this.mTimestamp == -1 ? System.currentTimeMillis() : this.mTimestamp;
|
||||||
|
info.mForecastList = this.mForecastList;
|
||||||
|
info.mTodaysHighTemp = this.mTodaysHighTemp;
|
||||||
|
info.mTodaysLowTemp = this.mTodaysLowTemp;
|
||||||
|
info.mKey = UUID.randomUUID().toString();
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidTempUnit(int unit) {
|
||||||
|
switch (unit) {
|
||||||
|
case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
|
||||||
|
case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidWindSpeedUnit(int unit) {
|
||||||
|
switch (unit) {
|
||||||
|
case WeatherContract.WeatherColumns.WindSpeedUnit.KPH:
|
||||||
|
case WeatherContract.WeatherColumns.WindSpeedUnit.MPH:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static boolean isValidWeatherCode(int code) {
|
||||||
|
if (code < WeatherContract.WeatherColumns.WeatherCode.WEATHER_CODE_MIN
|
||||||
|
|| code > WeatherContract.WeatherColumns.WeatherCode.WEATHER_CODE_MAX) {
|
||||||
|
if (code != WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return city name
|
||||||
|
*/
|
||||||
|
public String getCity() {
|
||||||
|
return mCity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return An implementation specific weather condition code
|
||||||
|
*/
|
||||||
|
public int getConditionCode() {
|
||||||
|
return mConditionCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return humidity
|
||||||
|
*/
|
||||||
|
public double getHumidity() {
|
||||||
|
return mHumidity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return time stamp when the request was processed
|
||||||
|
*/
|
||||||
|
public long getTimestamp() {
|
||||||
|
return mTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return wind direction (degrees)
|
||||||
|
*/
|
||||||
|
public double getWindDirection() {
|
||||||
|
return mWindDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return wind speed
|
||||||
|
*/
|
||||||
|
public double getWindSpeed() {
|
||||||
|
return mWindSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return wind speed unit
|
||||||
|
*/
|
||||||
|
public int getWindSpeedUnit() {
|
||||||
|
return mWindSpeedUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return current temperature
|
||||||
|
*/
|
||||||
|
public double getTemperature() {
|
||||||
|
return mTemperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return temperature unit
|
||||||
|
*/
|
||||||
|
public int getTemperatureUnit() {
|
||||||
|
return mTempUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return today's high temperature
|
||||||
|
*/
|
||||||
|
public double getTodaysHigh() {
|
||||||
|
return mTodaysHighTemp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return today's low temperature
|
||||||
|
*/
|
||||||
|
public double getTodaysLow() {
|
||||||
|
return mTodaysLowTemp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return List of {@link lineageos.weather.WeatherInfo.DayForecast}. This list will contain
|
||||||
|
* the forecast weather for the upcoming days. If you want to know today's high and low
|
||||||
|
* temperatures, use {@link WeatherInfo#getTodaysHigh()} and {@link WeatherInfo#getTodaysLow()}
|
||||||
|
*/
|
||||||
|
public List<DayForecast> getForecasts() {
|
||||||
|
return new ArrayList<>(mForecastList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WeatherInfo(Parcel parcel) {
|
||||||
|
// Read parcelable version via the Concierge
|
||||||
|
ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
|
||||||
|
int parcelableVersion = parcelInfo.getParcelVersion();
|
||||||
|
|
||||||
|
if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
|
||||||
|
mKey = parcel.readString();
|
||||||
|
mCity = parcel.readString();
|
||||||
|
mConditionCode = parcel.readInt();
|
||||||
|
mTemperature = parcel.readDouble();
|
||||||
|
mTempUnit = parcel.readInt();
|
||||||
|
mHumidity = parcel.readDouble();
|
||||||
|
mWindSpeed = parcel.readDouble();
|
||||||
|
mWindDirection = parcel.readDouble();
|
||||||
|
mWindSpeedUnit = parcel.readInt();
|
||||||
|
mTodaysHighTemp = parcel.readDouble();
|
||||||
|
mTodaysLowTemp = parcel.readDouble();
|
||||||
|
mTimestamp = parcel.readLong();
|
||||||
|
int forecastListSize = parcel.readInt();
|
||||||
|
mForecastList = new ArrayList<>();
|
||||||
|
while (forecastListSize > 0) {
|
||||||
|
mForecastList.add(DayForecast.CREATOR.createFromParcel(parcel));
|
||||||
|
forecastListSize--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete parcel info for the concierge
|
||||||
|
parcelInfo.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
// Tell the concierge to prepare the parcel
|
||||||
|
ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
|
||||||
|
|
||||||
|
// ==== ELDERBERRY =====
|
||||||
|
dest.writeString(mKey);
|
||||||
|
dest.writeString(mCity);
|
||||||
|
dest.writeInt(mConditionCode);
|
||||||
|
dest.writeDouble(mTemperature);
|
||||||
|
dest.writeInt(mTempUnit);
|
||||||
|
dest.writeDouble(mHumidity);
|
||||||
|
dest.writeDouble(mWindSpeed);
|
||||||
|
dest.writeDouble(mWindDirection);
|
||||||
|
dest.writeInt(mWindSpeedUnit);
|
||||||
|
dest.writeDouble(mTodaysHighTemp);
|
||||||
|
dest.writeDouble(mTodaysLowTemp);
|
||||||
|
dest.writeLong(mTimestamp);
|
||||||
|
dest.writeInt(mForecastList.size());
|
||||||
|
for (DayForecast dayForecast : mForecastList) {
|
||||||
|
dayForecast.writeToParcel(dest, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete parcel info for the concierge
|
||||||
|
parcelInfo.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<WeatherInfo> CREATOR =
|
||||||
|
new Parcelable.Creator<WeatherInfo>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WeatherInfo createFromParcel(Parcel source) {
|
||||||
|
return new WeatherInfo(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WeatherInfo[] newArray(int size) {
|
||||||
|
return new WeatherInfo[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents the weather forecast for a given day. Do not add low and high
|
||||||
|
* temperatures for the current day in this list. Use
|
||||||
|
* {@link WeatherInfo.Builder#setTodaysHigh(double)} and
|
||||||
|
* {@link WeatherInfo.Builder#setTodaysLow(double)} instead.
|
||||||
|
*/
|
||||||
|
public static class DayForecast implements Parcelable{
|
||||||
|
double mLow;
|
||||||
|
double mHigh;
|
||||||
|
int mConditionCode;
|
||||||
|
String mKey;
|
||||||
|
|
||||||
|
private DayForecast() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder class for {@link DayForecast}
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
double mLow = Double.NaN;
|
||||||
|
double mHigh = Double.NaN;
|
||||||
|
int mConditionCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param conditionCode A valid weather condition code. See
|
||||||
|
* {@link lineageos.providers.WeatherContract.WeatherColumns.WeatherCode} for valid
|
||||||
|
* values. Attempting to pass an invalid code will get you an
|
||||||
|
* IllegalArgumentException
|
||||||
|
*/
|
||||||
|
public Builder(int conditionCode) {
|
||||||
|
if (!isValidWeatherCode(conditionCode)) {
|
||||||
|
throw new IllegalArgumentException("Invalid weather condition code");
|
||||||
|
}
|
||||||
|
mConditionCode = conditionCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param high Forecast high temperature for this day. Attempting to pass an invalid
|
||||||
|
* double value will get you an IllegalArgumentException
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder setHigh(double high) {
|
||||||
|
if (Double.isNaN(high)) {
|
||||||
|
throw new IllegalArgumentException("Invalid high forecast temperature");
|
||||||
|
}
|
||||||
|
mHigh = high;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param low Forecast low temperate for this day. Attempting to pass an invalid double
|
||||||
|
* value will get you an IllegalArgumentException
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder setLow(double low) {
|
||||||
|
if (Double.isNaN(low)) {
|
||||||
|
throw new IllegalArgumentException("Invalid low forecast temperature");
|
||||||
|
}
|
||||||
|
mLow = low;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine all of the options that have been set and return a new {@link DayForecast}
|
||||||
|
* object
|
||||||
|
* @return {@link DayForecast}
|
||||||
|
*/
|
||||||
|
public DayForecast build() {
|
||||||
|
DayForecast forecast = new DayForecast();
|
||||||
|
forecast.mLow = this.mLow;
|
||||||
|
forecast.mHigh = this.mHigh;
|
||||||
|
forecast.mConditionCode = this.mConditionCode;
|
||||||
|
forecast.mKey = UUID.randomUUID().toString();
|
||||||
|
return forecast;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return forecasted low temperature
|
||||||
|
*/
|
||||||
|
public double getLow() {
|
||||||
|
return mLow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return not what you think. Returns the forecasted high temperature
|
||||||
|
*/
|
||||||
|
public double getHigh() {
|
||||||
|
return mHigh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return forecasted weather condition code. Implementation specific
|
||||||
|
*/
|
||||||
|
public int getConditionCode() {
|
||||||
|
return mConditionCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
// Tell the concierge to prepare the parcel
|
||||||
|
ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
|
||||||
|
|
||||||
|
// ==== ELDERBERRY =====
|
||||||
|
dest.writeString(mKey);
|
||||||
|
dest.writeDouble(mLow);
|
||||||
|
dest.writeDouble(mHigh);
|
||||||
|
dest.writeInt(mConditionCode);
|
||||||
|
|
||||||
|
// Complete parcel info for the concierge
|
||||||
|
parcelInfo.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<DayForecast> CREATOR =
|
||||||
|
new Parcelable.Creator<DayForecast>() {
|
||||||
|
@Override
|
||||||
|
public DayForecast createFromParcel(Parcel source) {
|
||||||
|
return new DayForecast(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DayForecast[] newArray(int size) {
|
||||||
|
return new DayForecast[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private DayForecast(Parcel parcel) {
|
||||||
|
// Read parcelable version via the Concierge
|
||||||
|
ParcelInfo parcelInfo = Concierge.receiveParcel(parcel);
|
||||||
|
int parcelableVersion = parcelInfo.getParcelVersion();
|
||||||
|
|
||||||
|
if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
|
||||||
|
mKey = parcel.readString();
|
||||||
|
mLow = parcel.readDouble();
|
||||||
|
mHigh = parcel.readDouble();
|
||||||
|
mConditionCode = parcel.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete parcel info for the concierge
|
||||||
|
parcelInfo.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new StringBuilder()
|
||||||
|
.append("{Low temp: ").append(mLow)
|
||||||
|
.append(" High temp: ").append(mHigh)
|
||||||
|
.append(" Condition code: ").append(mConditionCode)
|
||||||
|
.append("}").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) return false;
|
||||||
|
|
||||||
|
if (getClass() == obj.getClass()) {
|
||||||
|
DayForecast forecast = (DayForecast) obj;
|
||||||
|
return (TextUtils.equals(mKey, forecast.mKey));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder()
|
||||||
|
.append(" City Name: ").append(mCity)
|
||||||
|
.append(" Condition Code: ").append(mConditionCode)
|
||||||
|
.append(" Temperature: ").append(mTemperature)
|
||||||
|
.append(" Temperature Unit: ").append(mTempUnit)
|
||||||
|
.append(" Humidity: ").append(mHumidity)
|
||||||
|
.append(" Wind speed: ").append(mWindSpeed)
|
||||||
|
.append(" Wind direction: ").append(mWindDirection)
|
||||||
|
.append(" Wind Speed Unit: ").append(mWindSpeedUnit)
|
||||||
|
.append(" Today's high temp: ").append(mTodaysHighTemp)
|
||||||
|
.append(" Today's low temp: ").append(mTodaysLowTemp)
|
||||||
|
.append(" Timestamp: ").append(mTimestamp).append(" Forecasts: [");
|
||||||
|
for (DayForecast dayForecast : mForecastList) {
|
||||||
|
builder.append(dayForecast.toString());
|
||||||
|
}
|
||||||
|
return builder.append("]}").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) return false;
|
||||||
|
|
||||||
|
if (getClass() == obj.getClass()) {
|
||||||
|
WeatherInfo info = (WeatherInfo) obj;
|
||||||
|
return (TextUtils.equals(mKey, info.mKey));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
274
app/src/main/java/lineageos/weather/WeatherLocation.java
Normal file
274
app/src/main/java/lineageos/weather/WeatherLocation.java
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weather;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import lineageos.os.Build;
|
||||||
|
import lineageos.os.Concierge;
|
||||||
|
import lineageos.os.Concierge.ParcelInfo;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class representing a geographical location that a weather service provider can use to
|
||||||
|
* get weather data from. Each service provider will potentially populate objects of this class
|
||||||
|
* with different content, so make sure you don't preserve the values when a service provider
|
||||||
|
* is changed
|
||||||
|
*/
|
||||||
|
public final class WeatherLocation implements Parcelable{
|
||||||
|
private String mCityId;
|
||||||
|
private String mCity;
|
||||||
|
private String mState;
|
||||||
|
private String mPostal;
|
||||||
|
private String mCountryId;
|
||||||
|
private String mCountry;
|
||||||
|
private String mKey;
|
||||||
|
|
||||||
|
private WeatherLocation() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder class for {@link WeatherLocation}
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
String mCityId = "";
|
||||||
|
String mCity = "";
|
||||||
|
String mState = "";
|
||||||
|
String mPostal = "";
|
||||||
|
String mCountryId = "";
|
||||||
|
String mCountry = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cityId An identifier for the city (for example WOEID - Where On Earth IDentifier)
|
||||||
|
* @param cityName The name of the city
|
||||||
|
*/
|
||||||
|
public Builder(String cityId, String cityName) {
|
||||||
|
if (cityId == null || cityName == null) {
|
||||||
|
throw new IllegalArgumentException("Illegal to set city id AND city to null");
|
||||||
|
}
|
||||||
|
this.mCityId = cityId;
|
||||||
|
this.mCity = cityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cityName The name of the city
|
||||||
|
*/
|
||||||
|
public Builder(String cityName) {
|
||||||
|
if (cityName == null) {
|
||||||
|
throw new IllegalArgumentException("City name can't be null");
|
||||||
|
}
|
||||||
|
this.mCity = cityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param countryId An identifier for the country (for example ISO alpha-2, ISO alpha-3,
|
||||||
|
* ISO 3166-1 numeric-3, etc)
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder setCountryId(String countryId) {
|
||||||
|
if (countryId == null) {
|
||||||
|
throw new IllegalArgumentException("Country ID can't be null");
|
||||||
|
}
|
||||||
|
this.mCountryId = countryId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param country The country name
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder setCountry(String country) {
|
||||||
|
if (country == null) {
|
||||||
|
throw new IllegalArgumentException("Country can't be null");
|
||||||
|
}
|
||||||
|
this.mCountry = country;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param postalCode The postal/ZIP code
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder setPostalCode(String postalCode) {
|
||||||
|
if (postalCode == null) {
|
||||||
|
throw new IllegalArgumentException("Postal code/ZIP can't be null");
|
||||||
|
}
|
||||||
|
this.mPostal = postalCode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param state The state or territory where the city is located
|
||||||
|
* @return The {@link Builder} instance
|
||||||
|
*/
|
||||||
|
public Builder setState(String state) {
|
||||||
|
if (state == null) {
|
||||||
|
throw new IllegalArgumentException("State can't be null");
|
||||||
|
}
|
||||||
|
this.mState = state;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine all of the options that have been set and return a new {@link WeatherLocation}
|
||||||
|
* object
|
||||||
|
* @return {@link WeatherLocation}
|
||||||
|
*/
|
||||||
|
public WeatherLocation build() {
|
||||||
|
WeatherLocation weatherLocation = new WeatherLocation();
|
||||||
|
weatherLocation.mCityId = this.mCityId;
|
||||||
|
weatherLocation.mCity = this.mCity;
|
||||||
|
weatherLocation.mState = this.mState;
|
||||||
|
weatherLocation.mPostal = this.mPostal;
|
||||||
|
weatherLocation.mCountryId = this.mCountryId;
|
||||||
|
weatherLocation.mCountry = this.mCountry;
|
||||||
|
weatherLocation.mKey = UUID.randomUUID().toString();
|
||||||
|
return weatherLocation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The city ID. This method will return an empty string if the city ID was not set
|
||||||
|
*/
|
||||||
|
public String getCityId() {
|
||||||
|
return mCityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The city name. This method will return an empty string if the city name was not set
|
||||||
|
*/
|
||||||
|
public String getCity() {
|
||||||
|
return mCity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The state name. This method will return an empty string if the state was not set
|
||||||
|
*/
|
||||||
|
public String getState() {
|
||||||
|
return mState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The postal/ZIP code. This method will return an empty string if the postal/ZIP code
|
||||||
|
* was not set
|
||||||
|
*/
|
||||||
|
public String getPostalCode() {
|
||||||
|
return mPostal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The country ID. This method will return an empty string if the country ID was not set
|
||||||
|
*/
|
||||||
|
public String getCountryId() {
|
||||||
|
return mCountryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The country name. This method will return an empty string if the country ID was not
|
||||||
|
* set
|
||||||
|
*/
|
||||||
|
public String getCountry() {
|
||||||
|
return mCountry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WeatherLocation(Parcel in) {
|
||||||
|
// Read parcelable version via the Concierge
|
||||||
|
ParcelInfo parcelInfo = Concierge.receiveParcel(in);
|
||||||
|
int parcelableVersion = parcelInfo.getParcelVersion();
|
||||||
|
|
||||||
|
if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
|
||||||
|
mKey = in.readString();
|
||||||
|
mCityId = in.readString();
|
||||||
|
mCity = in.readString();
|
||||||
|
mState = in.readString();
|
||||||
|
mPostal = in.readString();
|
||||||
|
mCountryId = in.readString();
|
||||||
|
mCountry = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete parcel info for the concierge
|
||||||
|
parcelInfo.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<WeatherLocation> CREATOR = new Creator<WeatherLocation>() {
|
||||||
|
@Override
|
||||||
|
public WeatherLocation createFromParcel(Parcel in) {
|
||||||
|
return new WeatherLocation(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WeatherLocation[] newArray(int size) {
|
||||||
|
return new WeatherLocation[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
// Tell the concierge to prepare the parcel
|
||||||
|
ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
|
||||||
|
|
||||||
|
// ==== ELDERBERRY =====
|
||||||
|
dest.writeString(mKey);
|
||||||
|
dest.writeString(mCityId);
|
||||||
|
dest.writeString(mCity);
|
||||||
|
dest.writeString(mState);
|
||||||
|
dest.writeString(mPostal);
|
||||||
|
dest.writeString(mCountryId);
|
||||||
|
dest.writeString(mCountry);
|
||||||
|
|
||||||
|
// Complete parcel info for the concierge
|
||||||
|
parcelInfo.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new StringBuilder()
|
||||||
|
.append("{ City ID: ").append(mCityId)
|
||||||
|
.append(" City: ").append(mCity)
|
||||||
|
.append(" State: ").append(mState)
|
||||||
|
.append(" Postal/ZIP Code: ").append(mPostal)
|
||||||
|
.append(" Country Id: ").append(mCountryId)
|
||||||
|
.append(" Country: ").append(mCountry).append("}")
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) return false;
|
||||||
|
|
||||||
|
if (getClass() == obj.getClass()) {
|
||||||
|
WeatherLocation location = (WeatherLocation) obj;
|
||||||
|
return (TextUtils.equals(mKey, location.mKey));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
84
app/src/main/java/lineageos/weather/util/WeatherUtils.java
Normal file
84
app/src/main/java/lineageos/weather/util/WeatherUtils.java
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weather.util;
|
||||||
|
|
||||||
|
|
||||||
|
import lineageos.providers.WeatherContract;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to perform operations and formatting of weather data
|
||||||
|
*/
|
||||||
|
public class WeatherUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a temperature expressed in degrees Celsius to degrees Fahrenheit
|
||||||
|
* @param celsius temperature in Celsius
|
||||||
|
* @return the temperature in degrees Fahrenheit
|
||||||
|
*/
|
||||||
|
public static double celsiusToFahrenheit(double celsius) {
|
||||||
|
return ((celsius * (9d/5d)) + 32d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a temperature expressed in degrees Fahrenheit to degrees Celsius
|
||||||
|
* @param fahrenheit temperature in Fahrenheit
|
||||||
|
* @return the temperature in degrees Celsius
|
||||||
|
*/
|
||||||
|
public static double fahrenheitToCelsius(double fahrenheit) {
|
||||||
|
return ((fahrenheit - 32d) * (5d/9d));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of the temperature and unit supplied. The temperature value
|
||||||
|
* will be half-even rounded.
|
||||||
|
* @param temperature the temperature value
|
||||||
|
* @param tempUnit A valid {@link WeatherContract.WeatherColumns.TempUnit}
|
||||||
|
* @return A string with the format XX°F or XX°C (where XX is the temperature)
|
||||||
|
* depending on the temperature unit that was provided or null if an invalid unit is supplied
|
||||||
|
*/
|
||||||
|
public static String formatTemperature(double temperature, int tempUnit) {
|
||||||
|
if (!isValidTempUnit(tempUnit)) return null;
|
||||||
|
if (Double.isNaN(temperature)) return "-";
|
||||||
|
|
||||||
|
DecimalFormat noDigitsFormat = new DecimalFormat("0");
|
||||||
|
String noDigitsTemp = noDigitsFormat.format(temperature);
|
||||||
|
if (noDigitsTemp.equals("-0")) {
|
||||||
|
noDigitsTemp = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder formatted = new StringBuilder()
|
||||||
|
.append(noDigitsTemp).append("\u00b0");
|
||||||
|
if (tempUnit == WeatherContract.WeatherColumns.TempUnit.CELSIUS) {
|
||||||
|
formatted.append("C");
|
||||||
|
} else if (tempUnit == WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT) {
|
||||||
|
formatted.append("F");
|
||||||
|
}
|
||||||
|
return formatted.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isValidTempUnit(int unit) {
|
||||||
|
switch (unit) {
|
||||||
|
case WeatherContract.WeatherColumns.TempUnit.CELSIUS:
|
||||||
|
case WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
161
app/src/main/java/lineageos/weatherservice/ServiceRequest.java
Normal file
161
app/src/main/java/lineageos/weatherservice/ServiceRequest.java
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weatherservice;
|
||||||
|
|
||||||
|
|
||||||
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import lineageos.weatherservice.IWeatherProviderServiceClient;
|
||||||
|
import lineageos.weather.LineageWeatherManager;
|
||||||
|
import lineageos.weather.RequestInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a request submitted by the system to the active weather provider service
|
||||||
|
*/
|
||||||
|
public final class ServiceRequest {
|
||||||
|
|
||||||
|
private final RequestInfo mInfo;
|
||||||
|
private final IWeatherProviderServiceClient mClient;
|
||||||
|
|
||||||
|
private enum Status {
|
||||||
|
IN_PROGRESS, COMPLETED, CANCELLED, FAILED, REJECTED
|
||||||
|
}
|
||||||
|
private Status mStatus;
|
||||||
|
|
||||||
|
/* package */ ServiceRequest(RequestInfo info, IWeatherProviderServiceClient client) {
|
||||||
|
mInfo = info;
|
||||||
|
mClient = client;
|
||||||
|
mStatus = Status.IN_PROGRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the request information
|
||||||
|
* @return {@link lineageos.weather.RequestInfo}
|
||||||
|
*/
|
||||||
|
public RequestInfo getRequestInfo() {
|
||||||
|
return mInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should be called once the request has been completed
|
||||||
|
*/
|
||||||
|
public void complete(@NonNull ServiceRequestResult result) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (mStatus.equals(Status.IN_PROGRESS)) {
|
||||||
|
try {
|
||||||
|
final int requestType = mInfo.getRequestType();
|
||||||
|
switch (requestType) {
|
||||||
|
case RequestInfo.TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||||
|
case RequestInfo.TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||||
|
if (result.getWeatherInfo() == null) {
|
||||||
|
throw new IllegalStateException("The service request result doesn't"
|
||||||
|
+ " contain a valid WeatherInfo object");
|
||||||
|
}
|
||||||
|
mClient.setServiceRequestState(mInfo, result,
|
||||||
|
LineageWeatherManager.RequestStatus.COMPLETED);
|
||||||
|
break;
|
||||||
|
case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ:
|
||||||
|
if (result.getLocationLookupList() == null
|
||||||
|
|| result.getLocationLookupList().size() <= 0) {
|
||||||
|
//In case the user decided to mark this request as completed with
|
||||||
|
//null or empty list. It's not necessarily a failure
|
||||||
|
mClient.setServiceRequestState(mInfo, null,
|
||||||
|
LineageWeatherManager.RequestStatus.NO_MATCH_FOUND);
|
||||||
|
} else {
|
||||||
|
mClient.setServiceRequestState(mInfo, result,
|
||||||
|
LineageWeatherManager.RequestStatus.COMPLETED);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
}
|
||||||
|
mStatus = Status.COMPLETED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should be called if the service failed to process the request
|
||||||
|
* (no internet connection, time out, etc.)
|
||||||
|
*/
|
||||||
|
public void fail() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (mStatus.equals(Status.IN_PROGRESS)) {
|
||||||
|
try {
|
||||||
|
final int requestType = mInfo.getRequestType();
|
||||||
|
switch (requestType) {
|
||||||
|
case RequestInfo.TYPE_WEATHER_BY_GEO_LOCATION_REQ:
|
||||||
|
case RequestInfo.TYPE_WEATHER_BY_WEATHER_LOCATION_REQ:
|
||||||
|
mClient.setServiceRequestState(mInfo, null,
|
||||||
|
LineageWeatherManager.RequestStatus.FAILED);
|
||||||
|
break;
|
||||||
|
case RequestInfo.TYPE_LOOKUP_CITY_NAME_REQ:
|
||||||
|
mClient.setServiceRequestState(mInfo, null,
|
||||||
|
LineageWeatherManager.RequestStatus.FAILED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
}
|
||||||
|
mStatus = Status.FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should be called if the service decides not to honor the request. Note this
|
||||||
|
* method will accept only the following values.
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link lineageos.weather.LineageWeatherManager.RequestStatus#SUBMITTED_TOO_SOON}</li>
|
||||||
|
* <li>{@link lineageos.weather.LineageWeatherManager.RequestStatus#ALREADY_IN_PROGRESS}</li>
|
||||||
|
* </ul>
|
||||||
|
* Attempting to pass any other value will get you an IllegalArgumentException
|
||||||
|
* @param status
|
||||||
|
*/
|
||||||
|
public void reject(int status) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (mStatus.equals(Status.IN_PROGRESS)) {
|
||||||
|
switch (status) {
|
||||||
|
case LineageWeatherManager.RequestStatus.ALREADY_IN_PROGRESS:
|
||||||
|
case LineageWeatherManager.RequestStatus.SUBMITTED_TOO_SOON:
|
||||||
|
try {
|
||||||
|
mClient.setServiceRequestState(mInfo, null, status);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Can't reject with status " + status);
|
||||||
|
}
|
||||||
|
mStatus = Status.REJECTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the WeatherProviderService base class to notify we don't want this request anymore.
|
||||||
|
* The service implementing the WeatherProviderService will be notified of this action
|
||||||
|
* via onRequestCancelled()
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public void cancel() {
|
||||||
|
synchronized (this) {
|
||||||
|
mStatus = Status.CANCELLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weatherservice;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import lineageos.os.Build;
|
||||||
|
import lineageos.os.Concierge;
|
||||||
|
import lineageos.os.Concierge.ParcelInfo;
|
||||||
|
import lineageos.weather.WeatherLocation;
|
||||||
|
import lineageos.weather.WeatherInfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this class to build a request result.
|
||||||
|
*/
|
||||||
|
public final class ServiceRequestResult implements Parcelable {
|
||||||
|
|
||||||
|
private WeatherInfo mWeatherInfo;
|
||||||
|
private List<WeatherLocation> mLocationLookupList;
|
||||||
|
private String mKey;
|
||||||
|
|
||||||
|
private ServiceRequestResult() {}
|
||||||
|
|
||||||
|
private ServiceRequestResult(Parcel in) {
|
||||||
|
// Read parcelable version via the Concierge
|
||||||
|
ParcelInfo parcelInfo = Concierge.receiveParcel(in);
|
||||||
|
int parcelableVersion = parcelInfo.getParcelVersion();
|
||||||
|
|
||||||
|
if (parcelableVersion >= Build.LINEAGE_VERSION_CODES.ELDERBERRY) {
|
||||||
|
mKey = in.readString();
|
||||||
|
int hasWeatherInfo = in.readInt();
|
||||||
|
if (hasWeatherInfo == 1) {
|
||||||
|
mWeatherInfo = WeatherInfo.CREATOR.createFromParcel(in);
|
||||||
|
}
|
||||||
|
int hasLocationLookupList = in.readInt();
|
||||||
|
if (hasLocationLookupList == 1) {
|
||||||
|
mLocationLookupList = new ArrayList<>();
|
||||||
|
int listSize = in.readInt();
|
||||||
|
while (listSize > 0) {
|
||||||
|
mLocationLookupList.add(WeatherLocation.CREATOR.createFromParcel(in));
|
||||||
|
listSize--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete parcel info for the concierge
|
||||||
|
parcelInfo.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<ServiceRequestResult> CREATOR
|
||||||
|
= new Creator<ServiceRequestResult>() {
|
||||||
|
@Override
|
||||||
|
public ServiceRequestResult createFromParcel(Parcel in) {
|
||||||
|
return new ServiceRequestResult(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServiceRequestResult[] newArray(int size) {
|
||||||
|
return new ServiceRequestResult[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
// Tell the concierge to prepare the parcel
|
||||||
|
ParcelInfo parcelInfo = Concierge.prepareParcel(dest);
|
||||||
|
|
||||||
|
// ==== ELDERBERRY =====
|
||||||
|
dest.writeString(mKey);
|
||||||
|
if (mWeatherInfo != null) {
|
||||||
|
dest.writeInt(1);
|
||||||
|
mWeatherInfo.writeToParcel(dest, 0);
|
||||||
|
} else {
|
||||||
|
dest.writeInt(0);
|
||||||
|
}
|
||||||
|
if (mLocationLookupList != null) {
|
||||||
|
dest.writeInt(1);
|
||||||
|
dest.writeInt(mLocationLookupList.size());
|
||||||
|
for (WeatherLocation lookup : mLocationLookupList) {
|
||||||
|
lookup.writeToParcel(dest, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dest.writeInt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete parcel info for the concierge
|
||||||
|
parcelInfo.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder class for {@link ServiceRequestResult}
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private WeatherInfo mWeatherInfo;
|
||||||
|
private List<WeatherLocation> mLocationLookupList;
|
||||||
|
public Builder() {
|
||||||
|
this.mWeatherInfo = null;
|
||||||
|
this.mLocationLookupList = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param weatherInfo The WeatherInfo object holding the data that will be used to update
|
||||||
|
* the weather content provider
|
||||||
|
*/
|
||||||
|
public Builder(@NonNull WeatherInfo weatherInfo) {
|
||||||
|
if (weatherInfo == null) {
|
||||||
|
throw new IllegalArgumentException("WeatherInfo can't be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
mWeatherInfo = weatherInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param locations The list of WeatherLocation objects. The list should not be null
|
||||||
|
*/
|
||||||
|
public Builder(@NonNull List<WeatherLocation> locations) {
|
||||||
|
if (locations == null) {
|
||||||
|
throw new IllegalArgumentException("Weather location list can't be null");
|
||||||
|
}
|
||||||
|
mLocationLookupList = locations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link ServiceRequestResult} with the arguments
|
||||||
|
* supplied to this builder
|
||||||
|
* @return {@link ServiceRequestResult}
|
||||||
|
*/
|
||||||
|
public ServiceRequestResult build() {
|
||||||
|
ServiceRequestResult result = new ServiceRequestResult();
|
||||||
|
result.mWeatherInfo = this.mWeatherInfo;
|
||||||
|
result.mLocationLookupList = this.mLocationLookupList;
|
||||||
|
result.mKey = UUID.randomUUID().toString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The WeatherInfo object supplied by the weather provider service
|
||||||
|
*/
|
||||||
|
public WeatherInfo getWeatherInfo() {
|
||||||
|
return mWeatherInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The list of WeatherLocation objects supplied by the weather provider service
|
||||||
|
*/
|
||||||
|
public List<WeatherLocation> getLocationLookupList() {
|
||||||
|
return new ArrayList<>(mLocationLookupList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((mKey != null) ? mKey.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) return false;
|
||||||
|
|
||||||
|
if (getClass() == obj.getClass()) {
|
||||||
|
ServiceRequestResult request = (ServiceRequestResult) obj;
|
||||||
|
return (TextUtils.equals(mKey, request.mKey));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The CyanogenMod Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lineageos.weatherservice;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import lineageos.weather.RequestInfo;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class WeatherProviderService extends Service {
|
||||||
|
|
||||||
|
private Handler mHandler;
|
||||||
|
private IWeatherProviderServiceClient mClient;
|
||||||
|
private Set<ServiceRequest> mWeakRequestsSet
|
||||||
|
= Collections.newSetFromMap(new WeakHashMap<ServiceRequest, Boolean>());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link android.content.Intent} action that must be declared as handled by a service in
|
||||||
|
* its manifest for the system to recognize it as a weather provider service
|
||||||
|
*/
|
||||||
|
public static final String SERVICE_INTERFACE
|
||||||
|
= "lineageos.weatherservice.WeatherProviderService";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name under which a {@link WeatherProviderService} publishes information about itself.
|
||||||
|
* This meta-data must reference an XML resource containing
|
||||||
|
* a <code><weather-provider-service></code>
|
||||||
|
* tag.
|
||||||
|
*/
|
||||||
|
public static final String SERVICE_META_DATA = "lineageos.weatherservice";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final void attachBaseContext(Context base) {
|
||||||
|
super.attachBaseContext(base);
|
||||||
|
mHandler = new ServiceHandler(base.getMainLooper());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final IBinder onBind(Intent intent) {
|
||||||
|
return mBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final IWeatherProviderService.Stub mBinder = new IWeatherProviderService.Stub() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processWeatherUpdateRequest(final RequestInfo info) {
|
||||||
|
mHandler.obtainMessage(ServiceHandler.MSG_ON_NEW_REQUEST, info).sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processCityNameLookupRequest(final RequestInfo info) {
|
||||||
|
mHandler.obtainMessage(ServiceHandler.MSG_ON_NEW_REQUEST, info).sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setServiceClient(IWeatherProviderServiceClient client) {
|
||||||
|
mHandler.obtainMessage(ServiceHandler.MSG_SET_CLIENT, client).sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelOngoingRequests() {
|
||||||
|
synchronized (mWeakRequestsSet) {
|
||||||
|
for (final ServiceRequest request : mWeakRequestsSet) {
|
||||||
|
request.cancel();
|
||||||
|
mWeakRequestsSet.remove(request);
|
||||||
|
mHandler.obtainMessage(ServiceHandler.MSG_CANCEL_REQUEST, request)
|
||||||
|
.sendToTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelRequest(int requestId) {
|
||||||
|
synchronized (mWeakRequestsSet) {
|
||||||
|
for (final ServiceRequest request : mWeakRequestsSet) {
|
||||||
|
if (request.getRequestInfo().hashCode() == requestId) {
|
||||||
|
mWeakRequestsSet.remove(request);
|
||||||
|
request.cancel();
|
||||||
|
mHandler.obtainMessage(ServiceHandler.MSG_CANCEL_REQUEST, request)
|
||||||
|
.sendToTarget();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private class ServiceHandler extends Handler {
|
||||||
|
|
||||||
|
public ServiceHandler(Looper looper) {
|
||||||
|
super(looper);
|
||||||
|
}
|
||||||
|
public static final int MSG_SET_CLIENT = 1;
|
||||||
|
public static final int MSG_ON_NEW_REQUEST = 2;
|
||||||
|
public static final int MSG_CANCEL_REQUEST = 3;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_SET_CLIENT: {
|
||||||
|
mClient = (IWeatherProviderServiceClient) msg.obj;
|
||||||
|
if (mClient != null) {
|
||||||
|
onConnected();
|
||||||
|
} else {
|
||||||
|
onDisconnected();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case MSG_ON_NEW_REQUEST: {
|
||||||
|
RequestInfo info = (RequestInfo) msg.obj;
|
||||||
|
if (info != null) {
|
||||||
|
ServiceRequest request = new ServiceRequest(info, mClient);
|
||||||
|
synchronized (mWeakRequestsSet) {
|
||||||
|
mWeakRequestsSet.add(request);
|
||||||
|
}
|
||||||
|
onRequestSubmitted(request);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case MSG_CANCEL_REQUEST: {
|
||||||
|
ServiceRequest request = (ServiceRequest) msg.obj;
|
||||||
|
onRequestCancelled(request);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The system has connected to this service.
|
||||||
|
*/
|
||||||
|
protected void onConnected() {
|
||||||
|
/* Do nothing */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The system has disconnected from this service.
|
||||||
|
*/
|
||||||
|
protected void onDisconnected() {
|
||||||
|
/* Do nothing */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A new request has been submitted to this service
|
||||||
|
* @param request The service request to be processed by this service
|
||||||
|
*/
|
||||||
|
protected abstract void onRequestSubmitted(ServiceRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the system is not interested on this request anymore. Note that the service
|
||||||
|
* <b>has marked the request as cancelled</b> and you must stop any ongoing operation
|
||||||
|
* (such as pulling data from internet) that this service could've been performing to honor the
|
||||||
|
* request.
|
||||||
|
*
|
||||||
|
* @param request The request cancelled by the system
|
||||||
|
*/
|
||||||
|
protected abstract void onRequestCancelled(ServiceRequest request);
|
||||||
|
}
|
@ -112,6 +112,7 @@ public class GBApplication extends Application {
|
|||||||
public static final String ACTION_QUIT
|
public static final String ACTION_QUIT
|
||||||
= "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit";
|
= "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit";
|
||||||
public static final String ACTION_LANGUAGE_CHANGE = "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.language_change";
|
public static final String ACTION_LANGUAGE_CHANGE = "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.language_change";
|
||||||
|
public static final String ACTION_NEW_DATA = "nodomain.freeyourgadget.gadgetbridge.action.new_data";
|
||||||
|
|
||||||
private static GBApplication app;
|
private static GBApplication app;
|
||||||
|
|
||||||
@ -631,8 +632,9 @@ public class GBApplication extends Application {
|
|||||||
DaoSession daoSession = db.getDaoSession();
|
DaoSession daoSession = db.getDaoSession();
|
||||||
List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
|
List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
|
||||||
for (Device dbDevice : activeDevices) {
|
for (Device dbDevice : activeDevices) {
|
||||||
SharedPreferences.Editor deviceSharedPrefsEdit = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier()).edit();
|
SharedPreferences deviceSpecificSharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(dbDevice.getIdentifier());
|
||||||
if (sharedPrefs != null) {
|
if (deviceSpecificSharedPrefs != null) {
|
||||||
|
SharedPreferences.Editor deviceSharedPrefsEdit = deviceSpecificSharedPrefs.edit();
|
||||||
String preferenceKey = dbDevice.getIdentifier() + "_lastSportsActivityTimeMillis";
|
String preferenceKey = dbDevice.getIdentifier() + "_lastSportsActivityTimeMillis";
|
||||||
long lastSportsActivityTimeMillis = sharedPrefs.getLong(preferenceKey, 0);
|
long lastSportsActivityTimeMillis = sharedPrefs.getLong(preferenceKey, 0);
|
||||||
if (lastSportsActivityTimeMillis != 0) {
|
if (lastSportsActivityTimeMillis != 0) {
|
||||||
@ -707,10 +709,9 @@ public class GBApplication extends Application {
|
|||||||
if (newLanguage != null) {
|
if (newLanguage != null) {
|
||||||
deviceSharedPrefsEdit.putString("language", newLanguage);
|
deviceSharedPrefsEdit.putString("language", newLanguage);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
deviceSharedPrefsEdit.apply();
|
deviceSharedPrefsEdit.apply();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
editor.remove("amazfitbip_language");
|
editor.remove("amazfitbip_language");
|
||||||
editor.remove("bip_display_items");
|
editor.remove("bip_display_items");
|
||||||
editor.remove("cor_display_items");
|
editor.remove("cor_display_items");
|
||||||
|
@ -0,0 +1,225 @@
|
|||||||
|
/* Copyright (C) 2016-2019 0nse, Andreas Shimokawa, Carsten Pfeiffer
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge;
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.appwidget.AppWidgetManager;
|
||||||
|
import android.appwidget.AppWidgetProvider;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.widget.RemoteViews;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.WidgetAlarmsActivity;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
|
||||||
|
public class Widget extends AppWidgetProvider {
|
||||||
|
public static final String WIDGET_CLICK = "nodomain.freeyourgadget.gadgetbridge.WidgetClick";
|
||||||
|
public static final String APPWIDGET_DELETED = "nodomain.freeyourgadget.gadgetbridge.APPWIDGET_DELETED";
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(Widget.class);
|
||||||
|
static BroadcastReceiver broadcastReceiver = null;
|
||||||
|
|
||||||
|
private GBDevice getSelectedDevice() {
|
||||||
|
|
||||||
|
Context context = GBApplication.getContext();
|
||||||
|
|
||||||
|
if (!(context instanceof GBApplication)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
GBApplication gbApp = (GBApplication) context;
|
||||||
|
|
||||||
|
return gbApp.getDeviceManager().getSelectedDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] getSteps() {
|
||||||
|
Context context = GBApplication.getContext();
|
||||||
|
Calendar day = GregorianCalendar.getInstance();
|
||||||
|
|
||||||
|
if (!(context instanceof GBApplication)) {
|
||||||
|
return new int[]{0, 0, 0};
|
||||||
|
}
|
||||||
|
DailyTotals ds = new DailyTotals();
|
||||||
|
return ds.getDailyTotalsForAllDevices(day);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHM(long value) {
|
||||||
|
return DateTimeUtils.formatDurationHoursMinutes(value, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
|
||||||
|
int appWidgetId) {
|
||||||
|
|
||||||
|
GBDevice device = getSelectedDevice();
|
||||||
|
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
|
||||||
|
|
||||||
|
//onclick refresh
|
||||||
|
Intent intent = new Intent(context, Widget.class);
|
||||||
|
intent.setAction(WIDGET_CLICK);
|
||||||
|
PendingIntent refreshDataIntent = PendingIntent.getBroadcast(
|
||||||
|
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
//views.setOnClickPendingIntent(R.id.todaywidget_bottom_layout, refreshDataIntent);
|
||||||
|
views.setOnClickPendingIntent(R.id.todaywidget_header_bar, refreshDataIntent);
|
||||||
|
|
||||||
|
//open GB main window
|
||||||
|
Intent startMainIntent = new Intent(context, ControlCenterv2.class);
|
||||||
|
PendingIntent startMainPIntent = PendingIntent.getActivity(context, 0, startMainIntent, 0);
|
||||||
|
views.setOnClickPendingIntent(R.id.todaywidget_header_icon, startMainPIntent);
|
||||||
|
|
||||||
|
//alarms popup menu
|
||||||
|
Intent startAlarmListIntent = new Intent(context, WidgetAlarmsActivity.class);
|
||||||
|
PendingIntent startAlarmListPIntent = PendingIntent.getActivity(context, 0, startAlarmListIntent, 0);
|
||||||
|
views.setOnClickPendingIntent(R.id.todaywidget_header_plus, startAlarmListPIntent);
|
||||||
|
|
||||||
|
//charts, requires device
|
||||||
|
if (device != null) {
|
||||||
|
Intent startChartsIntent = new Intent(context, ChartsActivity.class);
|
||||||
|
startChartsIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||||
|
PendingIntent startChartsPIntent = PendingIntent.getActivity(context, 0, startChartsIntent, 0);
|
||||||
|
views.setOnClickPendingIntent(R.id.todaywidget_bottom_layout, startChartsPIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int[] DailyTotals = getSteps();
|
||||||
|
|
||||||
|
views.setTextViewText(R.id.todaywidget_steps, context.getString(R.string.widget_steps_label, (int) DailyTotals[0]));
|
||||||
|
views.setTextViewText(R.id.todaywidget_sleep, context.getString(R.string.widget_sleep_label, getHM((long) DailyTotals[1])));
|
||||||
|
|
||||||
|
if (device != null) {
|
||||||
|
String status = String.format("%1s", device.getStateString());
|
||||||
|
if (device.isConnected()) {
|
||||||
|
if (device.getBatteryLevel() > 1) {
|
||||||
|
status = String.format("Battery %1s%%", device.getBatteryLevel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
views.setTextViewText(R.id.todaywidget_device_status, status);
|
||||||
|
views.setTextViewText(R.id.todaywidget_device_name, device.getName());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instruct the widget manager to update the widget
|
||||||
|
appWidgetManager.updateAppWidget(appWidgetId, views);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshData() {
|
||||||
|
Context context = GBApplication.getContext();
|
||||||
|
GBDevice device = getSelectedDevice();
|
||||||
|
|
||||||
|
if (device == null || !device.isInitialized()) {
|
||||||
|
GB.toast(context,
|
||||||
|
context.getString(R.string.device_not_connected),
|
||||||
|
Toast.LENGTH_SHORT, GB.ERROR);
|
||||||
|
GBApplication.deviceService().connect();
|
||||||
|
GB.toast(context,
|
||||||
|
context.getString(R.string.connecting),
|
||||||
|
Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GB.toast(context,
|
||||||
|
context.getString(R.string.busy_task_fetch_activity_data),
|
||||||
|
Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
|
||||||
|
GBApplication.deviceService().onFetchRecordedData(RecordedDataTypes.TYPE_ACTIVITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateWidget() {
|
||||||
|
Context context = GBApplication.getContext();
|
||||||
|
|
||||||
|
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
|
||||||
|
ComponentName thisAppWidget = new ComponentName(context.getPackageName(), Widget.class.getName());
|
||||||
|
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget);
|
||||||
|
|
||||||
|
onUpdate(context, appWidgetManager, appWidgetIds);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||||
|
// There may be multiple widgets active, so update all of them
|
||||||
|
for (int appWidgetId : appWidgetIds) {
|
||||||
|
updateAppWidget(context, appWidgetManager, appWidgetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(Context context) {
|
||||||
|
if (broadcastReceiver == null) {
|
||||||
|
LOG.debug("gbwidget BROADCAST receiver initialized.");
|
||||||
|
broadcastReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
LOG.debug("gbwidget BROADCAST, action" + intent.getAction());
|
||||||
|
updateWidget();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(GBApplication.ACTION_NEW_DATA);
|
||||||
|
intentFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||||
|
LocalBroadcastManager.getInstance(context).registerReceiver(broadcastReceiver, intentFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisabled(Context context) {
|
||||||
|
|
||||||
|
if (broadcastReceiver != null) {
|
||||||
|
AndroidUtils.safeUnregisterBroadcastReceiver(context,broadcastReceiver);
|
||||||
|
broadcastReceiver = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
super.onReceive(context, intent);
|
||||||
|
LOG.debug("gbwidget LOCAL onReceive, action: " + intent.getAction());
|
||||||
|
//this handles widget re-connection after apk updates
|
||||||
|
if (WIDGET_CLICK.equals(intent.getAction())) {
|
||||||
|
if (broadcastReceiver == null) {
|
||||||
|
onEnabled(context);
|
||||||
|
}
|
||||||
|
refreshData();
|
||||||
|
//updateWidget();
|
||||||
|
} else if (APPWIDGET_DELETED.equals(intent.getAction())) {
|
||||||
|
onDisabled(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,10 @@ import android.widget.DatePicker;
|
|||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -45,9 +49,6 @@ import java.util.Calendar;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import androidx.core.content.FileProvider;
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter;
|
import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter;
|
||||||
@ -240,10 +241,10 @@ public class ActivitySummariesActivity extends AbstractListActivity<BaseActivity
|
|||||||
date.set(year, monthOfYear, dayOfMonth);
|
date.set(year, monthOfYear, dayOfMonth);
|
||||||
|
|
||||||
long timestamp = date.getTimeInMillis() - 1000;
|
long timestamp = date.getTimeInMillis() - 1000;
|
||||||
SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
|
SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress()).edit();
|
||||||
editor.remove(mGBDevice.getAddress() + "_" + "lastSportsActivityTimeMillis"); //FIXME: key reconstruction is BAD
|
editor.remove("lastSportsActivityTimeMillis"); //FIXME: key reconstruction is BAD
|
||||||
editor.putLong(mGBDevice.getAddress() + "_" + "lastSportsActivityTimeMillis", timestamp);
|
editor.putLong("lastSportsActivityTimeMillis", timestamp);
|
||||||
editor.commit();
|
editor.apply();
|
||||||
}
|
}
|
||||||
}, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
|
}, currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DATE)).show();
|
||||||
}
|
}
|
||||||
|
@ -27,17 +27,11 @@ import android.content.pm.PackageManager;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.telephony.PhoneStateListener;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|
||||||
import com.google.android.material.navigation.NavigationView;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
@ -50,6 +44,15 @@ import androidx.drawerlayout.widget.DrawerLayout;
|
|||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
import com.google.android.material.navigation.NavigationView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import de.cketti.library.changelog.ChangeLog;
|
import de.cketti.library.changelog.ChangeLog;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
@ -73,9 +76,14 @@ public class ControlCenterv2 extends AppCompatActivity
|
|||||||
|
|
||||||
private GBDeviceAdapterv2 mGBDeviceAdapter;
|
private GBDeviceAdapterv2 mGBDeviceAdapter;
|
||||||
private RecyclerView deviceListView;
|
private RecyclerView deviceListView;
|
||||||
|
private FloatingActionButton fab;
|
||||||
|
|
||||||
private boolean isLanguageInvalid = false;
|
private boolean isLanguageInvalid = false;
|
||||||
|
|
||||||
|
public static final int MENU_REFRESH_CODE=1;
|
||||||
|
|
||||||
|
private static PhoneStateListener fakeStateListener;
|
||||||
|
|
||||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
@ -103,14 +111,6 @@ public class ControlCenterv2 extends AppCompatActivity
|
|||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
FloatingActionButton fab = findViewById(R.id.fab);
|
|
||||||
fab.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
launchDiscoveryActivity();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
DrawerLayout drawer = findViewById(R.id.drawer_layout);
|
DrawerLayout drawer = findViewById(R.id.drawer_layout);
|
||||||
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
|
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
|
||||||
this, drawer, toolbar, R.string.controlcenter_navigation_drawer_open, R.string.controlcenter_navigation_drawer_close);
|
this, drawer, toolbar, R.string.controlcenter_navigation_drawer_open, R.string.controlcenter_navigation_drawer_close);
|
||||||
@ -132,6 +132,16 @@ public class ControlCenterv2 extends AppCompatActivity
|
|||||||
|
|
||||||
deviceListView.setAdapter(this.mGBDeviceAdapter);
|
deviceListView.setAdapter(this.mGBDeviceAdapter);
|
||||||
|
|
||||||
|
fab = findViewById(R.id.fab);
|
||||||
|
fab.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
launchDiscoveryActivity();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
showFabIfNeccessary();
|
||||||
|
|
||||||
/* uncomment to enable fixed-swipe to reveal more actions
|
/* uncomment to enable fixed-swipe to reveal more actions
|
||||||
|
|
||||||
ItemTouchHelper swipeToDismissTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
|
ItemTouchHelper swipeToDismissTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
|
||||||
@ -230,6 +240,15 @@ public class ControlCenterv2 extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
if (requestCode == MENU_REFRESH_CODE) {
|
||||||
|
showFabIfNeccessary();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
||||||
|
|
||||||
@ -239,7 +258,7 @@ public class ControlCenterv2 extends AppCompatActivity
|
|||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_settings:
|
case R.id.action_settings:
|
||||||
Intent settingsIntent = new Intent(this, SettingsActivity.class);
|
Intent settingsIntent = new Intent(this, SettingsActivity.class);
|
||||||
startActivity(settingsIntent);
|
startActivityForResult(settingsIntent, MENU_REFRESH_CODE);
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_debug:
|
case R.id.action_debug:
|
||||||
Intent debugIntent = new Intent(this, DebugActivity.class);
|
Intent debugIntent = new Intent(this, DebugActivity.class);
|
||||||
@ -253,6 +272,9 @@ public class ControlCenterv2 extends AppCompatActivity
|
|||||||
Intent blIntent = new Intent(this, AppBlacklistActivity.class);
|
Intent blIntent = new Intent(this, AppBlacklistActivity.class);
|
||||||
startActivity(blIntent);
|
startActivity(blIntent);
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.device_action_discover:
|
||||||
|
launchDiscoveryActivity();
|
||||||
|
return true;
|
||||||
case R.id.action_quit:
|
case R.id.action_quit:
|
||||||
GBApplication.quit();
|
GBApplication.quit();
|
||||||
return true;
|
return true;
|
||||||
@ -278,6 +300,7 @@ public class ControlCenterv2 extends AppCompatActivity
|
|||||||
"}";
|
"}";
|
||||||
return new ChangeLog(this, css);
|
return new ChangeLog(this, css);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void launchDiscoveryActivity() {
|
private void launchDiscoveryActivity() {
|
||||||
startActivity(new Intent(this, DiscoveryActivity.class));
|
startActivity(new Intent(this, DiscoveryActivity.class));
|
||||||
}
|
}
|
||||||
@ -286,6 +309,18 @@ public class ControlCenterv2 extends AppCompatActivity
|
|||||||
mGBDeviceAdapter.notifyDataSetChanged();
|
mGBDeviceAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showFabIfNeccessary() {
|
||||||
|
if (GBApplication.getPrefs().getBoolean("display_add_device_fab", true)) {
|
||||||
|
fab.show();
|
||||||
|
} else {
|
||||||
|
if (deviceListView.getChildCount() < 1) {
|
||||||
|
fab.show();
|
||||||
|
} else {
|
||||||
|
fab.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.M)
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
private void checkAndRequestPermissions() {
|
private void checkAndRequestPermissions() {
|
||||||
List<String> wantedPermissions = new ArrayList<>();
|
List<String> wantedPermissions = new ArrayList<>();
|
||||||
@ -321,7 +356,15 @@ public class ControlCenterv2 extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!wantedPermissions.isEmpty())
|
if (!wantedPermissions.isEmpty())
|
||||||
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[wantedPermissions.size()]), 0);
|
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0);
|
||||||
|
|
||||||
|
// HACK: On Lineage we have to do this so that the permission dialog pops up
|
||||||
|
if (fakeStateListener == null) {
|
||||||
|
fakeStateListener = new PhoneStateListener();
|
||||||
|
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
|
||||||
|
telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||||
|
telephonyManager.listen(fakeStateListener, PhoneStateListener.LISTEN_NONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLanguage(Locale language, boolean invalidateLanguage) {
|
public void setLanguage(Locale language, boolean invalidateLanguage) {
|
||||||
|
@ -17,12 +17,18 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
@ -42,10 +48,14 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.ImportExportSharedPreferences;
|
import nodomain.freeyourgadget.gadgetbridge.util.ImportExportSharedPreferences;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
|
||||||
|
|
||||||
public class DbManagementActivity extends AbstractGBActivity {
|
public class DbManagementActivity extends AbstractGBActivity {
|
||||||
@ -97,9 +107,68 @@ public class DbManagementActivity extends AbstractGBActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
|
boolean autoExportEnabled = prefs.getBoolean(GBPrefs.AUTO_EXPORT_ENABLED, false);
|
||||||
|
int autoExportInterval = prefs.getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
|
||||||
|
//returns an ugly content://...
|
||||||
|
//String autoExportLocation = prefs.getString(GBPrefs.AUTO_EXPORT_LOCATION, "");
|
||||||
|
|
||||||
|
int testExportVisibility = (autoExportInterval > 0 && autoExportEnabled) ? View.VISIBLE : View.GONE;
|
||||||
|
|
||||||
|
TextView autoExportLocation_label = findViewById(R.id.autoExportLocation_label);
|
||||||
|
autoExportLocation_label.setVisibility(testExportVisibility);
|
||||||
|
|
||||||
|
TextView autoExportLocation_intro = findViewById(R.id.autoExportLocation_intro);
|
||||||
|
autoExportLocation_intro.setVisibility(testExportVisibility);
|
||||||
|
|
||||||
|
TextView autoExportLocation_path = findViewById(R.id.autoExportLocation_path);
|
||||||
|
autoExportLocation_path.setVisibility(testExportVisibility);
|
||||||
|
autoExportLocation_path.setText(getAutoExportLocationSummary());
|
||||||
|
|
||||||
|
final Context context = getApplicationContext();
|
||||||
|
Button testExportDBButton = findViewById(R.id.testExportDBButton);
|
||||||
|
testExportDBButton.setVisibility(testExportVisibility);
|
||||||
|
testExportDBButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
sendBroadcast(new Intent(context, PeriodicExporter.class));
|
||||||
|
GB.toast(context,
|
||||||
|
context.getString(R.string.activity_DB_test_export_message),
|
||||||
|
Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//would rather re-use method of SettingsActivity... but lifecycle...
|
||||||
|
private String getAutoExportLocationSummary() {
|
||||||
|
String autoExportLocation = GBApplication.getPrefs().getString(GBPrefs.AUTO_EXPORT_LOCATION, null);
|
||||||
|
if (autoExportLocation == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
Uri uri = Uri.parse(autoExportLocation);
|
||||||
|
try {
|
||||||
|
return AndroidUtils.getFilePath(getApplicationContext(), uri);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
try {
|
||||||
|
Cursor cursor = getContentResolver().query(
|
||||||
|
uri,
|
||||||
|
new String[]{DocumentsContract.Document.COLUMN_DISPLAY_NAME},
|
||||||
|
null, null, null, null
|
||||||
|
);
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception fdfsdfds) {
|
||||||
|
LOG.warn("fuck");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean hasOldActivityDatabase() {
|
private boolean hasOldActivityDatabase() {
|
||||||
return new DBHelper(this).existsDB("ActivityDatabase");
|
return new DBHelper(this).existsDB("ActivityDatabase");
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ import android.widget.ListView;
|
|||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -77,6 +78,9 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
|||||||
|
|
||||||
private ScanCallback newLeScanCallback = null;
|
private ScanCallback newLeScanCallback = null;
|
||||||
|
|
||||||
|
// Disabled for testing, it seems worse for a few people
|
||||||
|
private final boolean disableNewBLEScanning = true;
|
||||||
|
|
||||||
private final Handler handler = new Handler();
|
private final Handler handler = new Handler();
|
||||||
|
|
||||||
private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
|
private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
|
||||||
@ -95,7 +99,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
|||||||
// continue with LE scan, if available
|
// continue with LE scan, if available
|
||||||
if (isScanning == Scanning.SCANNING_BT) {
|
if (isScanning == Scanning.SCANNING_BT) {
|
||||||
checkAndRequestLocationPermission();
|
checkAndRequestLocationPermission();
|
||||||
if (GBApplication.isRunningLollipopOrLater()) {
|
if (GBApplication.isRunningLollipopOrLater() && !disableNewBLEScanning) {
|
||||||
startDiscovery(Scanning.SCANNING_NEW_BTLE);
|
startDiscovery(Scanning.SCANNING_NEW_BTLE);
|
||||||
} else {
|
} else {
|
||||||
startDiscovery(Scanning.SCANNING_BTLE);
|
startDiscovery(Scanning.SCANNING_BTLE);
|
||||||
@ -297,7 +301,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putParcelableArrayList("deviceCandidates", deviceCandidates);
|
outState.putParcelableArrayList("deviceCandidates", deviceCandidates);
|
||||||
}
|
}
|
||||||
@ -652,7 +656,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
|||||||
super.onPause();
|
super.onPause();
|
||||||
stopBTDiscovery();
|
stopBTDiscovery();
|
||||||
stopBTLEDiscovery();
|
stopBTLEDiscovery();
|
||||||
if (GBApplication.isRunningLollipopOrLater()) {
|
if (GBApplication.isRunningLollipopOrLater() && !disableNewBLEScanning) {
|
||||||
stopNewBTLEDiscovery();
|
stopNewBTLEDiscovery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.ConfigActivity;
|
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.ConfigActivity;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity;
|
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||||
@ -97,6 +98,16 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pref = findPreference("pref_charts");
|
||||||
|
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
Intent enableIntent = new Intent(SettingsActivity.this, ChartsPreferencesActivity.class);
|
||||||
|
startActivity(enableIntent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
pref = findPreference("pref_key_miband");
|
pref = findPreference("pref_key_miband");
|
||||||
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
public class WidgetAlarmsActivity extends Activity implements View.OnClickListener {
|
||||||
|
|
||||||
|
TextView textView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Context appContext = this.getApplicationContext();
|
||||||
|
if (appContext instanceof GBApplication) {
|
||||||
|
GBApplication gbApp = (GBApplication) appContext;
|
||||||
|
GBDevice selectedDevice = gbApp.getDeviceManager().getSelectedDevice();
|
||||||
|
if (selectedDevice == null || !selectedDevice.isInitialized()) {
|
||||||
|
GB.toast(this,
|
||||||
|
this.getString(R.string.not_connected),
|
||||||
|
Toast.LENGTH_LONG, GB.WARN);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
setContentView(R.layout.widget_alarms_activity_list);
|
||||||
|
int userSleepDuration = new ActivityUser().getSleepDuration();
|
||||||
|
textView = findViewById(R.id.alarm5);
|
||||||
|
if (userSleepDuration > 0) {
|
||||||
|
Resources res = getResources();
|
||||||
|
textView.setText(String.format(res.getQuantityString(R.plurals.widget_alarm_target_hours, userSleepDuration, userSleepDuration)));
|
||||||
|
} else {
|
||||||
|
textView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
|
||||||
|
switch (v.getId()) {
|
||||||
|
case R.id.alarm1:
|
||||||
|
setAlarm(5);
|
||||||
|
break;
|
||||||
|
case R.id.alarm2:
|
||||||
|
setAlarm(10);
|
||||||
|
break;
|
||||||
|
case R.id.alarm3:
|
||||||
|
setAlarm(20);
|
||||||
|
break;
|
||||||
|
case R.id.alarm4:
|
||||||
|
setAlarm(60);
|
||||||
|
break;
|
||||||
|
case R.id.alarm5:
|
||||||
|
setAlarm(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//this is to prevent screen flashing during closing
|
||||||
|
new Handler().postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlarm(int duration) {
|
||||||
|
// current timestamp
|
||||||
|
GregorianCalendar calendar = new GregorianCalendar();
|
||||||
|
if (duration > 0) {
|
||||||
|
calendar.add(Calendar.MINUTE, duration);
|
||||||
|
} else {
|
||||||
|
int userSleepDuration = new ActivityUser().getSleepDuration();
|
||||||
|
// add preferred sleep duration
|
||||||
|
if (userSleepDuration > 0) {
|
||||||
|
calendar.add(Calendar.HOUR_OF_DAY, userSleepDuration);
|
||||||
|
} else { // probably testing
|
||||||
|
calendar.add(Calendar.MINUTE, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// overwrite the first alarm and activate it, without
|
||||||
|
|
||||||
|
Context appContext = this.getApplicationContext();
|
||||||
|
if (appContext instanceof GBApplication) {
|
||||||
|
GBApplication gbApp = (GBApplication) appContext;
|
||||||
|
GBDevice selectedDevice = gbApp.getDeviceManager().getSelectedDevice();
|
||||||
|
if (selectedDevice == null || !selectedDevice.isInitialized()) {
|
||||||
|
GB.toast(this,
|
||||||
|
this.getString(R.string.appwidget_not_connected),
|
||||||
|
Toast.LENGTH_LONG, GB.WARN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int hours = calendar.get(Calendar.HOUR_OF_DAY);
|
||||||
|
int minutes = calendar.get(Calendar.MINUTE);
|
||||||
|
|
||||||
|
GB.toast(this,
|
||||||
|
this.getString(R.string.appwidget_setting_alarm, hours, minutes),
|
||||||
|
Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
|
||||||
|
Alarm alarm = AlarmUtils.createSingleShot(0, true, calendar);
|
||||||
|
ArrayList<Alarm> alarms = new ArrayList<>(1);
|
||||||
|
alarms.add(alarm);
|
||||||
|
GBApplication.deviceService().onSetAlarms(alarms);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -429,7 +429,7 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
|||||||
startActivity(startIntent);
|
startActivity(startIntent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.appmanager_app_openinstore:
|
case R.id.appmanager_app_openinstore:
|
||||||
String url = "https://pebble-appstore.romanport.com/" + ((selectedApp.getType() == GBDeviceApp.Type.WATCHFACE) ? "watchfaces" : "watchapps") + "/0/?query=" + selectedApp.getName();
|
String url = "https://apps.rebble.io/en_US/search/" + ((selectedApp.getType() == GBDeviceApp.Type.WATCHFACE) ? "watchfaces" : "watchapps") + "/1/?native=true&?query=" + Uri.encode(selectedApp.getName());
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
intent.setData(Uri.parse(url));
|
intent.setData(Uri.parse(url));
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
@ -26,16 +26,20 @@ import android.os.Bundle;
|
|||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
import com.github.mikephil.charting.charts.BarChart;
|
import com.github.mikephil.charting.charts.BarChart;
|
||||||
import com.github.mikephil.charting.charts.BarLineChartBase;
|
import com.github.mikephil.charting.charts.BarLineChartBase;
|
||||||
import com.github.mikephil.charting.charts.Chart;
|
import com.github.mikephil.charting.charts.Chart;
|
||||||
import com.github.mikephil.charting.components.AxisBase;
|
|
||||||
import com.github.mikephil.charting.components.YAxis;
|
import com.github.mikephil.charting.components.YAxis;
|
||||||
import com.github.mikephil.charting.data.ChartData;
|
import com.github.mikephil.charting.data.ChartData;
|
||||||
import com.github.mikephil.charting.data.Entry;
|
import com.github.mikephil.charting.data.Entry;
|
||||||
import com.github.mikephil.charting.data.LineData;
|
import com.github.mikephil.charting.data.LineData;
|
||||||
import com.github.mikephil.charting.data.LineDataSet;
|
import com.github.mikephil.charting.data.LineDataSet;
|
||||||
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
|
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||||
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
|
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -51,10 +55,6 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
|
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
|
||||||
@ -572,7 +572,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
|||||||
lineData = new LineData();
|
lineData = new LineData();
|
||||||
}
|
}
|
||||||
|
|
||||||
IAxisValueFormatter xValueFormatter = new SampleXLabelFormatter(tsTranslation);
|
ValueFormatter xValueFormatter = new SampleXLabelFormatter(tsTranslation);
|
||||||
return new DefaultChartsData(lineData, xValueFormatter);
|
return new DefaultChartsData(lineData, xValueFormatter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -753,14 +753,14 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
|||||||
|
|
||||||
public static class DefaultChartsData<T extends ChartData<?>> extends ChartsData {
|
public static class DefaultChartsData<T extends ChartData<?>> extends ChartsData {
|
||||||
private final T data;
|
private final T data;
|
||||||
private IAxisValueFormatter xValueFormatter;
|
private ValueFormatter xValueFormatter;
|
||||||
|
|
||||||
public DefaultChartsData(T data, IAxisValueFormatter xValueFormatter) {
|
public DefaultChartsData(T data, ValueFormatter xValueFormatter) {
|
||||||
this.xValueFormatter = xValueFormatter;
|
this.xValueFormatter = xValueFormatter;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAxisValueFormatter getXValueFormatter() {
|
public ValueFormatter getXValueFormatter() {
|
||||||
return xValueFormatter;
|
return xValueFormatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -769,7 +769,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class SampleXLabelFormatter implements IAxisValueFormatter {
|
protected static class SampleXLabelFormatter extends ValueFormatter {
|
||||||
private final TimestampTranslation tsTranslation;
|
private final TimestampTranslation tsTranslation;
|
||||||
SimpleDateFormat annotationDateFormat = new SimpleDateFormat("HH:mm");
|
SimpleDateFormat annotationDateFormat = new SimpleDateFormat("HH:mm");
|
||||||
// SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
|
// SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
|
||||||
@ -781,7 +781,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
|||||||
}
|
}
|
||||||
// TODO: this does not work. Cannot use precomputed labels
|
// TODO: this does not work. Cannot use precomputed labels
|
||||||
@Override
|
@Override
|
||||||
public String getFormattedValue(float value, AxisBase axis) {
|
public String getFormattedValue(float value) {
|
||||||
cal.clear();
|
cal.clear();
|
||||||
int ts = (int) value;
|
int ts = (int) value;
|
||||||
cal.setTimeInMillis(tsTranslation.toOriginalValue(ts) * 1000L);
|
cal.setTimeInMillis(tsTranslation.toOriginalValue(ts) * 1000L);
|
||||||
@ -791,7 +791,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class PreformattedXIndexLabelFormatter implements IAxisValueFormatter {
|
protected static class PreformattedXIndexLabelFormatter extends ValueFormatter {
|
||||||
private ArrayList<String> xLabels;
|
private ArrayList<String> xLabels;
|
||||||
|
|
||||||
public PreformattedXIndexLabelFormatter(ArrayList<String> xLabels) {
|
public PreformattedXIndexLabelFormatter(ArrayList<String> xLabels) {
|
||||||
@ -799,7 +799,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
|||||||
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public String getFormattedValue(float value, AxisBase axis) {
|
public String getFormattedValue(float value) {
|
||||||
int index = (int) value;
|
int index = (int) value;
|
||||||
if (xLabels == null || index >= xLabels.size()) {
|
if (xLabels == null || index >= xLabels.size()) {
|
||||||
return String.valueOf(value);
|
return String.valueOf(value);
|
||||||
|
@ -37,8 +37,7 @@ import com.github.mikephil.charting.data.ChartData;
|
|||||||
import com.github.mikephil.charting.data.PieData;
|
import com.github.mikephil.charting.data.PieData;
|
||||||
import com.github.mikephil.charting.data.PieDataSet;
|
import com.github.mikephil.charting.data.PieDataSet;
|
||||||
import com.github.mikephil.charting.data.PieEntry;
|
import com.github.mikephil.charting.data.PieEntry;
|
||||||
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
|
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||||
import com.github.mikephil.charting.formatter.IValueFormatter;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -48,6 +47,7 @@ import java.util.Calendar;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
@ -58,7 +58,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
|
|||||||
|
|
||||||
public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||||
protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class);
|
protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class);
|
||||||
protected final int TOTAL_DAYS = 7;
|
protected final int TOTAL_DAYS = getRangeDays();
|
||||||
|
|
||||||
private Locale mLocale;
|
private Locale mLocale;
|
||||||
private int mTargetValue = 0;
|
private int mTargetValue = 0;
|
||||||
@ -87,6 +87,10 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
|||||||
setupLegend(mWeekChart);
|
setupLegend(mWeekChart);
|
||||||
mTodayPieChart.setCenterText(mcd.getDayData().centerText);
|
mTodayPieChart.setCenterText(mcd.getDayData().centerText);
|
||||||
mTodayPieChart.setData(mcd.getDayData().data);
|
mTodayPieChart.setData(mcd.getDayData().data);
|
||||||
|
//set custom renderer for 30days bar charts
|
||||||
|
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||||
|
mWeekChart.setRenderer(new AngledLabelsChartRenderer(mWeekChart, mWeekChart.getAnimator(), mWeekChart.getViewPortHandler()));
|
||||||
|
}
|
||||||
|
|
||||||
mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
|
mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
|
||||||
mWeekChart.setData(mcd.getWeekBeforeData().getData());
|
mWeekChart.setData(mcd.getWeekBeforeData().getData());
|
||||||
@ -102,6 +106,17 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
|||||||
// mBalanceView.setText(getBalanceMessage(balance));
|
// mBalanceView.setText(getBalanceMessage(balance));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getWeeksChartsLabel(Calendar day){
|
||||||
|
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||||
|
//month, show day date
|
||||||
|
return String.valueOf(day.get(Calendar.DAY_OF_MONTH));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
//week, show short day name
|
||||||
|
return day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private WeekChartsData<BarData> refreshWeekBeforeData(DBHandler db, BarChart barChart, Calendar day, GBDevice device) {
|
private WeekChartsData<BarData> refreshWeekBeforeData(DBHandler db, BarChart barChart, Calendar day, GBDevice device) {
|
||||||
day = (Calendar) day.clone(); // do not modify the caller's argument
|
day = (Calendar) day.clone(); // do not modify the caller's argument
|
||||||
day.add(Calendar.DATE, -TOTAL_DAYS);
|
day.add(Calendar.DATE, -TOTAL_DAYS);
|
||||||
@ -114,7 +129,7 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
|||||||
|
|
||||||
balance += calculateBalance(amounts);
|
balance += calculateBalance(amounts);
|
||||||
entries.add(new BarEntry(counter, getTotalsForActivityAmounts(amounts)));
|
entries.add(new BarEntry(counter, getTotalsForActivityAmounts(amounts)));
|
||||||
labels.add(day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale));
|
labels.add(getWeeksChartsLabel(day));
|
||||||
day.add(Calendar.DATE, 1);
|
day.add(Calendar.DATE, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,6 +145,27 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
|||||||
barChart.getAxisLeft().removeAllLimitLines();
|
barChart.getAxisLeft().removeAllLimitLines();
|
||||||
barChart.getAxisLeft().addLimitLine(target);
|
barChart.getAxisLeft().addLimitLine(target);
|
||||||
|
|
||||||
|
float average = 0;
|
||||||
|
if (TOTAL_DAYS > 0) {
|
||||||
|
average = Math.abs(balance / TOTAL_DAYS);
|
||||||
|
}
|
||||||
|
LimitLine average_line = new LimitLine(average);
|
||||||
|
average_line.setLabel(getString(R.string.average, getAverage(average)));
|
||||||
|
|
||||||
|
if (average > (mTargetValue)) {
|
||||||
|
average_line.setLineColor(Color.GREEN);
|
||||||
|
average_line.setTextColor(Color.GREEN);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
average_line.setLineColor(Color.RED);
|
||||||
|
average_line.setTextColor(Color.RED);
|
||||||
|
}
|
||||||
|
if (average > 0) {
|
||||||
|
if (GBApplication.getPrefs().getBoolean("charts_show_average", true)) {
|
||||||
|
barChart.getAxisLeft().addLimitLine(average_line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue));
|
return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,6 +351,16 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
|||||||
return amounts;
|
return amounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getRangeDays(){
|
||||||
|
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||||
|
return 30;}
|
||||||
|
else{
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract String getAverage(float value);
|
||||||
|
|
||||||
abstract int getGoal();
|
abstract int getGoal();
|
||||||
|
|
||||||
abstract int getOffsetHours();
|
abstract int getOffsetHours();
|
||||||
@ -325,11 +371,11 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
|||||||
|
|
||||||
abstract String[] getPieLabels();
|
abstract String[] getPieLabels();
|
||||||
|
|
||||||
abstract IValueFormatter getPieValueFormatter();
|
abstract ValueFormatter getPieValueFormatter();
|
||||||
|
|
||||||
abstract IValueFormatter getBarValueFormatter();
|
abstract ValueFormatter getBarValueFormatter();
|
||||||
|
|
||||||
abstract IAxisValueFormatter getYAxisFormatter();
|
abstract ValueFormatter getYAxisFormatter();
|
||||||
|
|
||||||
abstract int[] getColors();
|
abstract int[] getColors();
|
||||||
|
|
||||||
|
@ -28,15 +28,15 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
|
|
||||||
class ActivityAnalysis {
|
public class ActivityAnalysis {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ActivityAnalysis.class);
|
public static final Logger LOG = LoggerFactory.getLogger(ActivityAnalysis.class);
|
||||||
|
|
||||||
// store raw steps and duration
|
// store raw steps and duration
|
||||||
protected HashMap<Integer, Long> stats = new HashMap<Integer, Long>();
|
protected HashMap<Integer, Long> stats = new HashMap<Integer, Long>();
|
||||||
// max speed determined from samples
|
// max speed determined from samples
|
||||||
private int maxSpeed = 0;
|
private int maxSpeed = 0;
|
||||||
|
|
||||||
ActivityAmounts calculateActivityAmounts(List<? extends ActivitySample> samples) {
|
public ActivityAmounts calculateActivityAmounts(List<? extends ActivitySample> samples) {
|
||||||
ActivityAmount deepSleep = new ActivityAmount(ActivityKind.TYPE_DEEP_SLEEP);
|
ActivityAmount deepSleep = new ActivityAmount(ActivityKind.TYPE_DEEP_SLEEP);
|
||||||
ActivityAmount lightSleep = new ActivityAmount(ActivityKind.TYPE_LIGHT_SLEEP);
|
ActivityAmount lightSleep = new ActivityAmount(ActivityKind.TYPE_LIGHT_SLEEP);
|
||||||
ActivityAmount notWorn = new ActivityAmount(ActivityKind.TYPE_NOT_WORN);
|
ActivityAmount notWorn = new ActivityAmount(ActivityKind.TYPE_NOT_WORN);
|
||||||
|
@ -143,7 +143,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void renderCharts() {
|
protected void renderCharts() {
|
||||||
mChart.animateX(ANIM_TIME, Easing.EasingOption.EaseInOutQuart);
|
mChart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
|
||||||
// mChart.invalidate();
|
// mChart.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
|
||||||
|
import com.github.mikephil.charting.animation.ChartAnimator;
|
||||||
|
import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider;
|
||||||
|
import com.github.mikephil.charting.renderer.BarChartRenderer;
|
||||||
|
import com.github.mikephil.charting.utils.ViewPortHandler;
|
||||||
|
|
||||||
|
public class AngledLabelsChartRenderer extends BarChartRenderer {
|
||||||
|
AngledLabelsChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
|
||||||
|
super(chart, animator, viewPortHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawValue(Canvas canvas, String valueText, float x, float y, int color) {
|
||||||
|
|
||||||
|
mValuePaint.setColor(color);
|
||||||
|
|
||||||
|
//move position to the center of bar
|
||||||
|
x=x+8;
|
||||||
|
y=y-25;
|
||||||
|
|
||||||
|
canvas.save();
|
||||||
|
canvas.rotate(-90, x, y);
|
||||||
|
|
||||||
|
canvas.drawText(valueText, x, y, mValuePaint);
|
||||||
|
|
||||||
|
canvas.restore();
|
||||||
|
}}
|
@ -255,12 +255,24 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
if (requestCode == 1) {
|
||||||
|
this.recreate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.charts_fetch_activity_data:
|
case R.id.charts_fetch_activity_data:
|
||||||
fetchActivityData();
|
fetchActivityData();
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.prefs_charts_menu:
|
||||||
|
Intent settingsIntent = new Intent(this, ChartsPreferencesActivity.class);
|
||||||
|
startActivityForResult(settingsIntent,1);
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -338,6 +350,24 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
|||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getSleepTitle() {
|
||||||
|
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||||
|
return getString(R.string.weeksleepchart_sleep_a_month);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return getString(R.string.weeksleepchart_sleep_a_week);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStepsTitle() {
|
||||||
|
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||||
|
return getString(R.string.weekstepschart_steps_a_month);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return getString(R.string.weekstepschart_steps_a_week);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence getPageTitle(int position) {
|
public CharSequence getPageTitle(int position) {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
@ -346,9 +376,9 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
|||||||
case 1:
|
case 1:
|
||||||
return getString(R.string.sleepchart_your_sleep);
|
return getString(R.string.sleepchart_your_sleep);
|
||||||
case 2:
|
case 2:
|
||||||
return getString(R.string.weeksleepchart_sleep_a_week);
|
return getSleepTitle();
|
||||||
case 3:
|
case 3:
|
||||||
return getString(R.string.weekstepschart_steps_a_week);
|
return getStepsTitle();
|
||||||
case 4:
|
case 4:
|
||||||
return getString(R.string.stats_title);
|
return getString(R.string.stats_title);
|
||||||
case 5:
|
case 5:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/* Copyright (C) 2015-2019 Andreas Shimokawa
|
/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Christian
|
||||||
|
Fischer, Daniele Gobbetti, José Rebelo, Szymon Tomasz Stefanek
|
||||||
|
|
||||||
This file is part of Gadgetbridge.
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
@ -14,12 +15,17 @@
|
|||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
|
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||||
|
|
||||||
public class GBDeviceEventSleepMonitorResult extends GBDeviceEvent {
|
import android.os.Bundle;
|
||||||
// FIXME: this is just the low-level data from Morpheuz, we need something generic
|
|
||||||
public int smartalarm_from = -1; // time in minutes relative from 0:00 for smart alarm (earliest)
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
public int smartalarm_to = -1;// time in minutes relative from 0:00 for smart alarm (latest)
|
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivity;
|
||||||
public int recording_base_timestamp = -1; // timestamp for the first "point", all folowing are +10 minutes offset each
|
|
||||||
public int alarm_gone_off = -1; // time in minutes relative from 0:00 when alarm gone off
|
public class ChartsPreferencesActivity extends AbstractSettingsActivity {
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
addPreferencesFromResource(R.xml.charts_preferences);
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
|
|
||||||
|
public class SleepAnalysis {
|
||||||
|
|
||||||
|
public static final long MIN_SESSION_LENGTH = 5 * 60;
|
||||||
|
public static final long MAX_WAKE_PHASE_LENGTH = 2 * 60 * 60;
|
||||||
|
|
||||||
|
public List<SleepSession> calculateSleepSessions(List<? extends ActivitySample> samples) {
|
||||||
|
List<SleepSession> result = new ArrayList<>();
|
||||||
|
|
||||||
|
ActivitySample previousSample = null;
|
||||||
|
Date sleepStart = null;
|
||||||
|
Date sleepEnd = null;
|
||||||
|
long lightSleepDuration = 0;
|
||||||
|
long deepSleepDuration = 0;
|
||||||
|
long durationSinceLastSleep = 0;
|
||||||
|
|
||||||
|
for (ActivitySample sample : samples) {
|
||||||
|
if (isSleep(sample)) {
|
||||||
|
if (sleepStart == null)
|
||||||
|
sleepStart = getDateFromSample(sample);
|
||||||
|
sleepEnd = getDateFromSample(sample);
|
||||||
|
|
||||||
|
durationSinceLastSleep = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousSample != null) {
|
||||||
|
long durationSinceLastSample = sample.getTimestamp() - previousSample.getTimestamp();
|
||||||
|
if (sample.getKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
|
||||||
|
lightSleepDuration += durationSinceLastSample;
|
||||||
|
} else if (sample.getKind() == ActivityKind.TYPE_DEEP_SLEEP) {
|
||||||
|
deepSleepDuration += durationSinceLastSample;
|
||||||
|
} else {
|
||||||
|
durationSinceLastSleep += durationSinceLastSample;
|
||||||
|
if (sleepStart != null && durationSinceLastSleep > MAX_WAKE_PHASE_LENGTH) {
|
||||||
|
if (lightSleepDuration + deepSleepDuration > MIN_SESSION_LENGTH)
|
||||||
|
result.add(new SleepSession(sleepStart, sleepEnd, lightSleepDuration, deepSleepDuration));
|
||||||
|
sleepStart = null;
|
||||||
|
sleepEnd = null;
|
||||||
|
lightSleepDuration = 0;
|
||||||
|
deepSleepDuration = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousSample = sample;
|
||||||
|
}
|
||||||
|
if (lightSleepDuration + deepSleepDuration > MIN_SESSION_LENGTH) {
|
||||||
|
result.add(new SleepSession(sleepStart, sleepEnd, lightSleepDuration, deepSleepDuration));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSleep(ActivitySample sample) {
|
||||||
|
return sample.getKind() == ActivityKind.TYPE_DEEP_SLEEP || sample.getKind() == ActivityKind.TYPE_LIGHT_SLEEP;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date getDateFromSample(ActivitySample sample) {
|
||||||
|
return new Date(sample.getTimestamp() * 1000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class SleepSession {
|
||||||
|
private final Date sleepStart;
|
||||||
|
private final Date sleepEnd;
|
||||||
|
private final long lightSleepDuration;
|
||||||
|
private final long deepSleepDuration;
|
||||||
|
|
||||||
|
private SleepSession(Date sleepStart,
|
||||||
|
Date sleepEnd,
|
||||||
|
long lightSleepDuration,
|
||||||
|
long deepSleepDuration) {
|
||||||
|
this.sleepStart = sleepStart;
|
||||||
|
this.sleepEnd = sleepEnd;
|
||||||
|
this.lightSleepDuration = lightSleepDuration;
|
||||||
|
this.deepSleepDuration = deepSleepDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getSleepStart() {
|
||||||
|
return sleepStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getSleepEnd() {
|
||||||
|
return sleepEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLightSleepDuration() {
|
||||||
|
return lightSleepDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getDeepSleepDuration() {
|
||||||
|
return deepSleepDuration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,29 +32,27 @@ import com.github.mikephil.charting.charts.PieChart;
|
|||||||
import com.github.mikephil.charting.components.LegendEntry;
|
import com.github.mikephil.charting.components.LegendEntry;
|
||||||
import com.github.mikephil.charting.components.XAxis;
|
import com.github.mikephil.charting.components.XAxis;
|
||||||
import com.github.mikephil.charting.components.YAxis;
|
import com.github.mikephil.charting.components.YAxis;
|
||||||
import com.github.mikephil.charting.data.Entry;
|
|
||||||
import com.github.mikephil.charting.data.LineData;
|
import com.github.mikephil.charting.data.LineData;
|
||||||
import com.github.mikephil.charting.data.PieData;
|
import com.github.mikephil.charting.data.PieData;
|
||||||
import com.github.mikephil.charting.data.PieDataSet;
|
import com.github.mikephil.charting.data.PieDataSet;
|
||||||
import com.github.mikephil.charting.data.PieEntry;
|
import com.github.mikephil.charting.data.PieEntry;
|
||||||
import com.github.mikephil.charting.formatter.IValueFormatter;
|
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||||
import com.github.mikephil.charting.utils.ViewPortHandler;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.charts.SleepAnalysis.SleepSession;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||||
@ -83,44 +81,40 @@ public class SleepChartFragment extends AbstractChartFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<? extends ActivitySample> samples) {
|
private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<? extends ActivitySample> samples) {
|
||||||
ActivityAnalysis analysis = new ActivityAnalysis();
|
SleepAnalysis sleepAnalysis = new SleepAnalysis();
|
||||||
ActivityAmounts amounts = analysis.calculateActivityAmounts(samples);
|
List<SleepSession> sleepSessions = sleepAnalysis.calculateSleepSessions(samples);
|
||||||
|
|
||||||
PieData data = new PieData();
|
PieData data = new PieData();
|
||||||
List<PieEntry> entries = new ArrayList<>();
|
|
||||||
List<Integer> colors = new ArrayList<>();
|
|
||||||
// int index = 0;
|
|
||||||
long totalSeconds = 0;
|
|
||||||
|
|
||||||
Date startSleep = null;
|
|
||||||
Date endSleep = null;
|
|
||||||
|
|
||||||
for (ActivityAmount amount : amounts.getAmounts()) {
|
final long lightSleepDuration = calculateLightSleepDuration(sleepSessions);
|
||||||
if ((amount.getActivityKind() & ActivityKind.TYPE_SLEEP) != 0) {
|
final long deepSleepDuration = calculateDeepSleepDuration(sleepSessions);
|
||||||
long value = amount.getTotalSeconds();
|
|
||||||
if(startSleep == null){
|
final long totalSeconds = lightSleepDuration + deepSleepDuration;
|
||||||
startSleep = amount.getStartDate();
|
|
||||||
|
final List<PieEntry> entries;
|
||||||
|
final List<Integer> colors;
|
||||||
|
|
||||||
|
if (sleepSessions.isEmpty()) {
|
||||||
|
entries = Collections.emptyList();
|
||||||
|
colors = Collections.emptyList();
|
||||||
} else {
|
} else {
|
||||||
if(startSleep.after(amount.getStartDate()))
|
entries = Arrays.asList(
|
||||||
startSleep = amount.getStartDate();
|
new PieEntry(lightSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_light_sleep)),
|
||||||
}
|
new PieEntry(deepSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_deep_sleep))
|
||||||
if(endSleep == null){
|
);
|
||||||
endSleep = amount.getEndDate();
|
colors = Arrays.asList(
|
||||||
} else {
|
getColorFor(ActivityKind.TYPE_LIGHT_SLEEP),
|
||||||
if(endSleep.before(amount.getEndDate()))
|
getColorFor(ActivityKind.TYPE_DEEP_SLEEP)
|
||||||
endSleep = amount.getEndDate();
|
);
|
||||||
}
|
|
||||||
totalSeconds += value;
|
|
||||||
// entries.add(new PieEntry(value, index++));
|
|
||||||
entries.add(new PieEntry(value, amount.getName(getActivity())));
|
|
||||||
colors.add(getColorFor(amount.getActivityKind()));
|
|
||||||
// data.addXValue(amount.getName(getActivity()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
String totalSleep = DateTimeUtils.formatDurationHoursMinutes(totalSeconds, TimeUnit.SECONDS);
|
String totalSleep = DateTimeUtils.formatDurationHoursMinutes(totalSeconds, TimeUnit.SECONDS);
|
||||||
PieDataSet set = new PieDataSet(entries, "");
|
PieDataSet set = new PieDataSet(entries, "");
|
||||||
set.setValueFormatter(new IValueFormatter() {
|
set.setValueFormatter(new ValueFormatter() {
|
||||||
@Override
|
@Override
|
||||||
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
|
public String getFormattedValue(float value) {
|
||||||
return DateTimeUtils.formatDurationHoursMinutes((long) value, TimeUnit.SECONDS);
|
return DateTimeUtils.formatDurationHoursMinutes((long) value, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -132,27 +126,54 @@ public class SleepChartFragment extends AbstractChartFragment {
|
|||||||
data.setDataSet(set);
|
data.setDataSet(set);
|
||||||
|
|
||||||
//setupLegend(pieChart);
|
//setupLegend(pieChart);
|
||||||
return new MySleepChartsData(totalSleep, data, startSleep, endSleep);
|
return new MySleepChartsData(totalSleep, data, sleepSessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long calculateLightSleepDuration(List<SleepSession> sleepSessions) {
|
||||||
|
long result = 0;
|
||||||
|
for (SleepSession sleepSession : sleepSessions) {
|
||||||
|
result += sleepSession.getLightSleepDuration();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long calculateDeepSleepDuration(List<SleepSession> sleepSessions) {
|
||||||
|
long result = 0;
|
||||||
|
for (SleepSession sleepSession : sleepSessions) {
|
||||||
|
result += sleepSession.getDeepSleepDuration();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateChartsnUIThread(ChartsData chartsData) {
|
protected void updateChartsnUIThread(ChartsData chartsData) {
|
||||||
MyChartsData mcd = (MyChartsData) chartsData;
|
MyChartsData mcd = (MyChartsData) chartsData;
|
||||||
mSleepAmountChart.setCenterText(mcd.getPieData().getTotalSleep());
|
MySleepChartsData pieData = mcd.getPieData();
|
||||||
mSleepAmountChart.setData(mcd.getPieData().getPieData());
|
mSleepAmountChart.setCenterText(pieData.getTotalSleep());
|
||||||
|
mSleepAmountChart.setData(pieData.getPieData());
|
||||||
|
|
||||||
mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
|
mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
|
||||||
mActivityChart.getXAxis().setValueFormatter(mcd.getChartsData().getXValueFormatter());
|
mActivityChart.getXAxis().setValueFormatter(mcd.getChartsData().getXValueFormatter());
|
||||||
mActivityChart.setData(mcd.getChartsData().getData());
|
mActivityChart.setData(mcd.getChartsData().getData());
|
||||||
|
|
||||||
if (mcd.getPieData().getStartSleep() != null && mcd.getPieData().getEndSleep() != null) {
|
|
||||||
mSleepchartInfo.setText(getContext().getString(
|
mSleepchartInfo.setText(buildYouSleptText(pieData));
|
||||||
R.string.you_slept,
|
|
||||||
DateTimeUtils.timeToString(mcd.getPieData().getStartSleep()),
|
|
||||||
DateTimeUtils.timeToString(mcd.getPieData().getEndSleep())));
|
|
||||||
} else {
|
|
||||||
mSleepchartInfo.setText(getContext().getString(R.string.you_did_not_sleep));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String buildYouSleptText(MySleepChartsData pieData) {
|
||||||
|
final StringBuilder result = new StringBuilder();
|
||||||
|
if (pieData.getSleepSessions().isEmpty()) {
|
||||||
|
result.append(getContext().getString(R.string.you_did_not_sleep));
|
||||||
|
} else {
|
||||||
|
for (SleepSession sleepSession : pieData.getSleepSessions()) {
|
||||||
|
result.append(getContext().getString(
|
||||||
|
R.string.you_slept,
|
||||||
|
DateTimeUtils.timeToString(sleepSession.getSleepStart()),
|
||||||
|
DateTimeUtils.timeToString(sleepSession.getSleepEnd())));
|
||||||
|
result.append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -269,21 +290,19 @@ public class SleepChartFragment extends AbstractChartFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void renderCharts() {
|
protected void renderCharts() {
|
||||||
mActivityChart.animateX(ANIM_TIME, Easing.EasingOption.EaseInOutQuart);
|
mActivityChart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
|
||||||
mSleepAmountChart.invalidate();
|
mSleepAmountChart.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MySleepChartsData extends ChartsData {
|
private static class MySleepChartsData extends ChartsData {
|
||||||
private String totalSleep;
|
private String totalSleep;
|
||||||
private final PieData pieData;
|
private final PieData pieData;
|
||||||
private @Nullable Date startSleep;
|
private final List<SleepSession> sleepSessions;
|
||||||
private @Nullable Date endSleep;
|
|
||||||
|
|
||||||
public MySleepChartsData(String totalSleep, PieData pieData, @Nullable Date startSleep, @Nullable Date endSleep) {
|
public MySleepChartsData(String totalSleep, PieData pieData, List<SleepSession> sleepSessions) {
|
||||||
this.totalSleep = totalSleep;
|
this.totalSleep = totalSleep;
|
||||||
this.pieData = pieData;
|
this.pieData = pieData;
|
||||||
this.startSleep = startSleep;
|
this.sleepSessions = sleepSessions;
|
||||||
this.endSleep = endSleep;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PieData getPieData() {
|
public PieData getPieData() {
|
||||||
@ -294,14 +313,8 @@ public class SleepChartFragment extends AbstractChartFragment {
|
|||||||
return totalSleep;
|
return totalSleep;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public List<SleepSession> getSleepSessions() {
|
||||||
public Date getStartSleep() {
|
return sleepSessions;
|
||||||
return startSleep;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Date getEndSleep() {
|
|
||||||
return endSleep;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||||
|
|
||||||
import com.github.mikephil.charting.components.AxisBase;
|
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||||
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
|
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@ -25,7 +24,7 @@ import java.util.Calendar;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
public class TimestampValueFormatter implements IAxisValueFormatter {
|
public class TimestampValueFormatter extends ValueFormatter {
|
||||||
private final Calendar cal;
|
private final Calendar cal;
|
||||||
// private DateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
|
// private DateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
|
||||||
private DateFormat dateFormat;
|
private DateFormat dateFormat;
|
||||||
@ -42,7 +41,7 @@ public class TimestampValueFormatter implements IAxisValueFormatter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFormattedValue(float value, AxisBase axis) {
|
public String getFormattedValue(float value) {
|
||||||
cal.setTimeInMillis((int) value * 1000L);
|
cal.setTimeInMillis((int) value * 1000L);
|
||||||
Date date = cal.getTime();
|
Date date = cal.getTime();
|
||||||
String dateString = dateFormat.format(date);
|
String dateString = dateFormat.format(date);
|
||||||
|
@ -17,13 +17,9 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||||
|
|
||||||
import com.github.mikephil.charting.charts.Chart;
|
import com.github.mikephil.charting.charts.Chart;
|
||||||
import com.github.mikephil.charting.components.AxisBase;
|
|
||||||
import com.github.mikephil.charting.components.Legend;
|
import com.github.mikephil.charting.components.Legend;
|
||||||
import com.github.mikephil.charting.components.LegendEntry;
|
import com.github.mikephil.charting.components.LegendEntry;
|
||||||
import com.github.mikephil.charting.data.Entry;
|
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||||
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
|
|
||||||
import com.github.mikephil.charting.formatter.IValueFormatter;
|
|
||||||
import com.github.mikephil.charting.utils.ViewPortHandler;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -40,8 +36,13 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
|||||||
public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
||||||
@Override
|
@Override
|
||||||
public String getTitle() {
|
public String getTitle() {
|
||||||
|
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||||
|
return getString(R.string.weeksleepchart_sleep_a_month);
|
||||||
|
}
|
||||||
|
else{
|
||||||
return getString(R.string.weeksleepchart_sleep_a_week);
|
return getString(R.string.weeksleepchart_sleep_a_week);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
String getPieDescription(int targetValue) {
|
String getPieDescription(int targetValue) {
|
||||||
@ -110,30 +111,30 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
IValueFormatter getPieValueFormatter() {
|
ValueFormatter getPieValueFormatter() {
|
||||||
return new IValueFormatter() {
|
return new ValueFormatter() {
|
||||||
@Override
|
@Override
|
||||||
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
|
public String getFormattedValue(float value) {
|
||||||
return formatPieValue((long) value);
|
return formatPieValue((long) value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
IValueFormatter getBarValueFormatter() {
|
ValueFormatter getBarValueFormatter() {
|
||||||
return new IValueFormatter() {
|
return new ValueFormatter() {
|
||||||
@Override
|
@Override
|
||||||
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
|
public String getFormattedValue(float value) {
|
||||||
return DateTimeUtils.minutesToHHMM((int) value);
|
return DateTimeUtils.minutesToHHMM((int) value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
IAxisValueFormatter getYAxisFormatter() {
|
ValueFormatter getYAxisFormatter() {
|
||||||
return new IAxisValueFormatter() {
|
return new ValueFormatter() {
|
||||||
@Override
|
@Override
|
||||||
public String getFormattedValue(float value, AxisBase axis) {
|
public String getFormattedValue(float value) {
|
||||||
return DateTimeUtils.minutesToHHMM((int) value);
|
return DateTimeUtils.minutesToHHMM((int) value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -167,4 +168,10 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
|||||||
private String getHM(long value) {
|
private String getHM(long value) {
|
||||||
return DateTimeUtils.formatDurationHoursMinutes(value, TimeUnit.MINUTES);
|
return DateTimeUtils.formatDurationHoursMinutes(value, TimeUnit.MINUTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String getAverage(float value) {
|
||||||
|
return getHM((long)value);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,7 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||||
|
|
||||||
import com.github.mikephil.charting.charts.Chart;
|
import com.github.mikephil.charting.charts.Chart;
|
||||||
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
|
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||||
import com.github.mikephil.charting.formatter.IValueFormatter;
|
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
@ -30,8 +29,13 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
|||||||
public class WeekStepsChartFragment extends AbstractWeekChartFragment {
|
public class WeekStepsChartFragment extends AbstractWeekChartFragment {
|
||||||
@Override
|
@Override
|
||||||
public String getTitle() {
|
public String getTitle() {
|
||||||
|
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||||
|
return getString(R.string.weekstepschart_steps_a_month);
|
||||||
|
}
|
||||||
|
else{
|
||||||
return getString(R.string.weekstepschart_steps_a_week);
|
return getString(R.string.weekstepschart_steps_a_week);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
String getPieDescription(int targetValue) {
|
String getPieDescription(int targetValue) {
|
||||||
@ -77,17 +81,17 @@ public class WeekStepsChartFragment extends AbstractWeekChartFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
IValueFormatter getPieValueFormatter() {
|
ValueFormatter getPieValueFormatter() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
IValueFormatter getBarValueFormatter() {
|
ValueFormatter getBarValueFormatter() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
IAxisValueFormatter getYAxisFormatter() {
|
ValueFormatter getYAxisFormatter() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,4 +117,9 @@ public class WeekStepsChartFragment extends AbstractWeekChartFragment {
|
|||||||
} else
|
} else
|
||||||
return getString(R.string.no_data);
|
return getString(R.string.no_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String getAverage(float value) {
|
||||||
|
return String.format("%.0f", value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,18 +16,21 @@ import org.slf4j.LoggerFactory;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
|
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreferenceFragment;
|
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreferenceFragment;
|
||||||
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DATEFORMAT;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISCONNECT_NOTIFICATION;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISCONNECT_NOTIFICATION;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISCONNECT_NOTIFICATION_END;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISCONNECT_NOTIFICATION_END;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISCONNECT_NOTIFICATION_START;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISCONNECT_NOTIFICATION_START;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ITEMS;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ON_LIFT_END;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ON_LIFT_END;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ON_LIFT_START;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DISPLAY_ON_LIFT_START;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_EXPOSE_HR_THIRDPARTY;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_LANGUAGE;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB_END;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB_END;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB_OFF;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_DO_NOT_DISTURB_OFF;
|
||||||
@ -282,8 +285,10 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat {
|
|||||||
|
|
||||||
addPreferenceHandlerFor(PREF_SWIPE_UNLOCK);
|
addPreferenceHandlerFor(PREF_SWIPE_UNLOCK);
|
||||||
addPreferenceHandlerFor(PREF_MI2_DATEFORMAT);
|
addPreferenceHandlerFor(PREF_MI2_DATEFORMAT);
|
||||||
addPreferenceHandlerFor("dateformat");
|
addPreferenceHandlerFor(PREF_DATEFORMAT);
|
||||||
addPreferenceHandlerFor(HuamiConst.PREF_DISPLAY_ITEMS);
|
addPreferenceHandlerFor(PREF_DISPLAY_ITEMS);
|
||||||
|
addPreferenceHandlerFor(PREF_LANGUAGE);
|
||||||
|
addPreferenceHandlerFor(PREF_EXPOSE_HR_THIRDPARTY);
|
||||||
|
|
||||||
String displayOnLiftState = prefs.getString(PREF_ACTIVATE_DISPLAY_ON_LIFT, PREF_DO_NOT_DISTURB_OFF);
|
String displayOnLiftState = prefs.getString(PREF_ACTIVATE_DISPLAY_ON_LIFT, PREF_DO_NOT_DISTURB_OFF);
|
||||||
boolean displayOnLiftScheduled = displayOnLiftState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
|
boolean displayOnLiftScheduled = displayOnLiftState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
|
||||||
|
@ -33,11 +33,14 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getPrefs;
|
||||||
|
|
||||||
public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractDeviceCoordinator.class);
|
private static final Logger LOG = LoggerFactory.getLogger(AbstractDeviceCoordinator.class);
|
||||||
@ -69,6 +72,17 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
|||||||
if (gbDevice.isConnected() || gbDevice.isConnecting()) {
|
if (gbDevice.isConnected() || gbDevice.isConnecting()) {
|
||||||
GBApplication.deviceService().disconnect();
|
GBApplication.deviceService().disconnect();
|
||||||
}
|
}
|
||||||
|
Prefs prefs = getPrefs();
|
||||||
|
String lastDevice = prefs.getPreferences().getString("last_device_address","");
|
||||||
|
if (gbDevice.getAddress() == lastDevice){
|
||||||
|
LOG.debug("#1605 removing last device");
|
||||||
|
prefs.getPreferences().edit().remove("last_device_address").apply();
|
||||||
|
}
|
||||||
|
String macAddress = prefs.getPreferences().getString(MiBandConst.PREF_MIBAND_ADDRESS,"");
|
||||||
|
if (gbDevice.getAddress() == macAddress){
|
||||||
|
LOG.debug("#1605 removing devel miband");
|
||||||
|
prefs.getPreferences().edit().remove(MiBandConst.PREF_MIBAND_ADDRESS).apply();
|
||||||
|
}
|
||||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||||
DaoSession session = dbHandler.getDaoSession();
|
DaoSession session = dbHandler.getDaoSession();
|
||||||
Device device = DBHelper.findDevice(gbDevice, session);
|
Device device = DBHelper.findDevice(gbDevice, session);
|
||||||
|
@ -59,6 +59,10 @@ public class HuamiConst {
|
|||||||
public static final String PREF_DISCONNECT_NOTIFICATION_END = "disconnect_notification_end";
|
public static final String PREF_DISCONNECT_NOTIFICATION_END = "disconnect_notification_end";
|
||||||
|
|
||||||
public static final String PREF_DISPLAY_ITEMS = "display_items";
|
public static final String PREF_DISPLAY_ITEMS = "display_items";
|
||||||
|
public static final String PREF_LANGUAGE = "language";
|
||||||
|
public static final String PREF_DATEFORMAT = "dateformat";
|
||||||
|
public static final String PREF_EXPOSE_HR_THIRDPARTY = "expose_hr_thirdparty";
|
||||||
|
public static final String PREF_USE_CUSTOM_FONT = "use_custom_font";
|
||||||
|
|
||||||
public static int toActivityKind(int rawType) {
|
public static int toActivityKind(int rawType) {
|
||||||
switch (rawType) {
|
switch (rawType) {
|
||||||
|
@ -197,6 +197,11 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator {
|
|||||||
return prefs.getStringSet(HuamiConst.PREF_DISPLAY_ITEMS, null);
|
return prefs.getStringSet(HuamiConst.PREF_DISPLAY_ITEMS, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean getUseCustomFont(String deviceAddress) {
|
||||||
|
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(deviceAddress);
|
||||||
|
return prefs.getBoolean(HuamiConst.PREF_USE_CUSTOM_FONT, false);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean getGoalNotification() {
|
public static boolean getGoalNotification() {
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
return prefs.getBoolean(MiBandConst.PREF_MI2_GOAL_NOTIFICATION, false);
|
return prefs.getBoolean(MiBandConst.PREF_MI2_GOAL_NOTIFICATION, false);
|
||||||
@ -251,6 +256,11 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator {
|
|||||||
return prefs.getBoolean(MiBandConst.PREF_SWIPE_UNLOCK, false);
|
return prefs.getBoolean(MiBandConst.PREF_SWIPE_UNLOCK, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean getExposeHRThirdParty(String deviceAddress) {
|
||||||
|
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
|
||||||
|
return prefs.getBoolean(HuamiConst.PREF_EXPOSE_HR_THIRDPARTY, false);
|
||||||
|
}
|
||||||
|
|
||||||
protected static Date getTimePreference(String key, String defaultValue, String deviceAddress) {
|
protected static Date getTimePreference(String key, String defaultValue, String deviceAddress) {
|
||||||
Prefs prefs;
|
Prefs prefs;
|
||||||
|
|
||||||
|
@ -49,6 +49,8 @@ public class HuamiService {
|
|||||||
// service uuid fee1
|
// service uuid fee1
|
||||||
public static final UUID UUID_CHARACTERISTIC_AUTH = UUID.fromString("00000009-0000-3512-2118-0009af100700");
|
public static final UUID UUID_CHARACTERISTIC_AUTH = UUID.fromString("00000009-0000-3512-2118-0009af100700");
|
||||||
public static final UUID UUID_CHARACTERISTIC_DEVICEEVENT = UUID.fromString("00000010-0000-3512-2118-0009af100700");
|
public static final UUID UUID_CHARACTERISTIC_DEVICEEVENT = UUID.fromString("00000010-0000-3512-2118-0009af100700");
|
||||||
|
public static final UUID UUID_CHARACTERISTIC_AUDIO = UUID.fromString("00000012-0000-3512-2118-0009af100700");
|
||||||
|
public static final UUID UUID_CHARACTERISTIC_AUDIODATA = UUID.fromString("00000013-0000-3512-2118-0009af100700");
|
||||||
|
|
||||||
public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER = UUID.fromString("00000020-0000-3512-2118-0009af100700");
|
public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER = UUID.fromString("00000020-0000-3512-2118-0009af100700");
|
||||||
|
|
||||||
@ -137,6 +139,8 @@ public class HuamiService {
|
|||||||
public static final byte[] DATEFORMAT_TIME_12_HOURS = new byte[] {ENDPOINT_DISPLAY, 0x02, 0x0, 0x0 };
|
public static final byte[] DATEFORMAT_TIME_12_HOURS = new byte[] {ENDPOINT_DISPLAY, 0x02, 0x0, 0x0 };
|
||||||
public static final byte[] DATEFORMAT_TIME_24_HOURS = new byte[] {ENDPOINT_DISPLAY, 0x02, 0x0, 0x1 };
|
public static final byte[] DATEFORMAT_TIME_24_HOURS = new byte[] {ENDPOINT_DISPLAY, 0x02, 0x0, 0x1 };
|
||||||
public static final byte[] DATEFORMAT_DATE_MM_DD_YYYY = new byte[]{ENDPOINT_DISPLAY, 30, 0x00, 'M', 'M', '/', 'd', 'd', '/', 'y', 'y', 'y', 'y'};
|
public static final byte[] DATEFORMAT_DATE_MM_DD_YYYY = new byte[]{ENDPOINT_DISPLAY, 30, 0x00, 'M', 'M', '/', 'd', 'd', '/', 'y', 'y', 'y', 'y'};
|
||||||
|
public static final byte[] COMMAND_ENBALE_HR_CONNECTION = new byte[]{ENDPOINT_DISPLAY, 0x01, 0x00, 0x01};
|
||||||
|
public static final byte[] COMMAND_DISABLE_HR_CONNECTION = new byte[]{ENDPOINT_DISPLAY, 0x01, 0x00, 0x00};
|
||||||
public static final byte[] COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x01};
|
public static final byte[] COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x01};
|
||||||
public static final byte[] COMMAND_DISABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x00};
|
public static final byte[] COMMAND_DISABLE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x00};
|
||||||
public static final byte[] COMMAND_SCHEDULE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
|
public static final byte[] COMMAND_SCHEDULE_DISPLAY_ON_LIFT_WRIST = new byte[]{ENDPOINT_DISPLAY, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
@ -47,7 +47,7 @@ public class AmazfitBipCoordinator extends HuamiCoordinator {
|
|||||||
try {
|
try {
|
||||||
BluetoothDevice device = candidate.getDevice();
|
BluetoothDevice device = candidate.getDevice();
|
||||||
String name = device.getName();
|
String name = device.getName();
|
||||||
if (name != null && name.equalsIgnoreCase("Amazfit Bip Watch")) {
|
if (name != null && (name.equalsIgnoreCase("Amazfit Bip Watch") || name.equalsIgnoreCase("Amazfit Bip Lite"))) {
|
||||||
return DeviceType.AMAZFITBIP;
|
return DeviceType.AMAZFITBIP;
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@ -81,8 +81,10 @@ public class AmazfitBipCoordinator extends HuamiCoordinator {
|
|||||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||||
return new int[]{
|
return new int[]{
|
||||||
R.xml.devicesettings_amazfitbip,
|
R.xml.devicesettings_amazfitbip,
|
||||||
|
R.xml.devicesettings_custom_emoji_font,
|
||||||
R.xml.devicesettings_liftwrist_display,
|
R.xml.devicesettings_liftwrist_display,
|
||||||
R.xml.devicesettings_disconnectnotification,
|
R.xml.devicesettings_disconnectnotification,
|
||||||
|
R.xml.devicesettings_expose_hr_thirdparty,
|
||||||
R.xml.devicesettings_pairingkey
|
R.xml.devicesettings_pairingkey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -84,8 +84,11 @@ public class AmazfitCorCoordinator extends HuamiCoordinator {
|
|||||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||||
return new int[]{
|
return new int[]{
|
||||||
R.xml.devicesettings_amazfitcor,
|
R.xml.devicesettings_amazfitcor,
|
||||||
|
R.xml.devicesettings_custom_emoji_font,
|
||||||
R.xml.devicesettings_liftwrist_display,
|
R.xml.devicesettings_liftwrist_display,
|
||||||
R.xml.devicesettings_disconnectnotification,
|
R.xml.devicesettings_disconnectnotification,
|
||||||
R.xml.devicesettings_pairingkey};
|
R.xml.devicesettings_expose_hr_thirdparty,
|
||||||
|
R.xml.devicesettings_pairingkey
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,7 @@ public class MiBand2Coordinator extends HuamiCoordinator {
|
|||||||
R.xml.devicesettings_donotdisturb_withauto,
|
R.xml.devicesettings_donotdisturb_withauto,
|
||||||
R.xml.devicesettings_liftwrist_display,
|
R.xml.devicesettings_liftwrist_display,
|
||||||
R.xml.devicesettings_rotatewrist_cycleinfo,
|
R.xml.devicesettings_rotatewrist_cycleinfo,
|
||||||
|
R.xml.devicesettings_expose_hr_thirdparty,
|
||||||
R.xml.devicesettings_pairingkey
|
R.xml.devicesettings_pairingkey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -108,6 +108,7 @@ public class MiBand3Coordinator extends HuamiCoordinator {
|
|||||||
R.xml.devicesettings_donotdisturb_withauto,
|
R.xml.devicesettings_donotdisturb_withauto,
|
||||||
R.xml.devicesettings_liftwrist_display,
|
R.xml.devicesettings_liftwrist_display,
|
||||||
R.xml.devicesettings_swipeunlock,
|
R.xml.devicesettings_swipeunlock,
|
||||||
|
R.xml.devicesettings_expose_hr_thirdparty,
|
||||||
R.xml.devicesettings_pairingkey
|
R.xml.devicesettings_pairingkey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.EN
|
|||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY_ITEMS;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY_ITEMS;
|
||||||
|
|
||||||
public class MiBand3Service {
|
public class MiBand3Service {
|
||||||
public static final byte[] COMMAND_CHANGE_SCREENS = new byte[]{ENDPOINT_DISPLAY_ITEMS, DISPLAY_ITEM_BIT_CLOCK, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00};
|
public static final byte[] COMMAND_CHANGE_SCREENS = new byte[]{ENDPOINT_DISPLAY_ITEMS, DISPLAY_ITEM_BIT_CLOCK, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||||
public static final byte[] COMMAND_ENABLE_BAND_SCREEN_UNLOCK = new byte[]{ENDPOINT_DISPLAY, 0x16, 0x00, 0x01};
|
public static final byte[] COMMAND_ENABLE_BAND_SCREEN_UNLOCK = new byte[]{ENDPOINT_DISPLAY, 0x16, 0x00, 0x01};
|
||||||
public static final byte[] COMMAND_DISABLE_BAND_SCREEN_UNLOCK = new byte[]{ENDPOINT_DISPLAY, 0x16, 0x00, 0x00};
|
public static final byte[] COMMAND_DISABLE_BAND_SCREEN_UNLOCK = new byte[]{ENDPOINT_DISPLAY, 0x16, 0x00, 0x00};
|
||||||
public static final byte[] COMMAND_NIGHT_MODE_OFF = new byte[]{0x1a, 0x00};
|
public static final byte[] COMMAND_NIGHT_MODE_OFF = new byte[]{0x1a, 0x00};
|
||||||
|
@ -90,9 +90,9 @@ public class MiBand4Coordinator extends HuamiCoordinator {
|
|||||||
R.xml.devicesettings_miband3,
|
R.xml.devicesettings_miband3,
|
||||||
R.xml.devicesettings_dateformat,
|
R.xml.devicesettings_dateformat,
|
||||||
R.xml.devicesettings_nightmode,
|
R.xml.devicesettings_nightmode,
|
||||||
R.xml.devicesettings_donotdisturb_withauto,
|
|
||||||
R.xml.devicesettings_liftwrist_display,
|
R.xml.devicesettings_liftwrist_display,
|
||||||
R.xml.devicesettings_swipeunlock,
|
R.xml.devicesettings_swipeunlock,
|
||||||
|
R.xml.devicesettings_expose_hr_thirdparty,
|
||||||
R.xml.devicesettings_pairingkey
|
R.xml.devicesettings_pairingkey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
/* Copyright (C) 2019 Andreas Shimokawa
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd02;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||||
|
|
||||||
|
public class MijiaLywsd02Coordinator extends AbstractDeviceCoordinator {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||||
|
String name = candidate.getDevice().getName();
|
||||||
|
if (name != null && name.equals("LYWSD02")) {
|
||||||
|
return DeviceType.MIJIA_LYWSD02;
|
||||||
|
}
|
||||||
|
return DeviceType.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceType getDeviceType() {
|
||||||
|
return DeviceType.MIJIA_LYWSD02;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBondingStyle(GBDevice deviceCandidate) {
|
||||||
|
return BONDING_STYLE_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Activity> getPairingActivity() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsActivityDataFetching() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsActivityTracking() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsScreenshots() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAlarmSlotCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsSmartWakeup(GBDevice device) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getManufacturer() {
|
||||||
|
return "Xiaomi";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsAppsManagement() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Activity> getAppsManagementActivity() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsCalendarEvents() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsRealtimeData() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsWeather() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsFindDevice() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
|
||||||
|
// nothing to delete, yet
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ import android.content.Context;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||||
@ -34,6 +34,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||||
|
|
||||||
public class VibratissimoCoordinator extends AbstractDeviceCoordinator {
|
public class VibratissimoCoordinator extends AbstractDeviceCoordinator {
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||||
String name = candidate.getDevice().getName();
|
String name = candidate.getDevice().getName();
|
||||||
@ -129,7 +130,7 @@ public class VibratissimoCoordinator extends AbstractDeviceCoordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
|
||||||
// nothing to delete, yet
|
// nothing to delete, yet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,4 +158,7 @@ public class ZeTimeCoordinator extends AbstractDeviceCoordinator {
|
|||||||
public int getBondingStyle(GBDevice device) {
|
public int getBondingStyle(GBDevice device) {
|
||||||
return BONDING_STYLE_NONE;
|
return BONDING_STYLE_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsUnicodeEmojis() { return true; }
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,225 @@
|
|||||||
|
/* Copyright (C) 2017-2019 Andreas Shimokawa
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.externalevents;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lineageos.weather.LineageWeatherManager;
|
||||||
|
import lineageos.weather.WeatherInfo;
|
||||||
|
import lineageos.weather.WeatherLocation;
|
||||||
|
import lineageos.weather.util.WeatherUtils;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
|
||||||
|
import static lineageos.providers.WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT;
|
||||||
|
import static lineageos.providers.WeatherContract.WeatherColumns.WeatherCode.ISOLATED_THUNDERSHOWERS;
|
||||||
|
import static lineageos.providers.WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE;
|
||||||
|
import static lineageos.providers.WeatherContract.WeatherColumns.WeatherCode.SCATTERED_SNOW_SHOWERS;
|
||||||
|
import static lineageos.providers.WeatherContract.WeatherColumns.WeatherCode.SCATTERED_THUNDERSTORMS;
|
||||||
|
import static lineageos.providers.WeatherContract.WeatherColumns.WeatherCode.SHOWERS;
|
||||||
|
import static lineageos.providers.WeatherContract.WeatherColumns.WindSpeedUnit.MPH;
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||||
|
public class LineageOsWeatherReceiver extends BroadcastReceiver implements LineageWeatherManager.WeatherUpdateRequestListener, LineageWeatherManager.LookupCityRequestListener {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(LineageOsWeatherReceiver.class);
|
||||||
|
|
||||||
|
private WeatherLocation weatherLocation = null;
|
||||||
|
private Context mContext;
|
||||||
|
private PendingIntent mPendingIntent = null;
|
||||||
|
|
||||||
|
public LineageOsWeatherReceiver() {
|
||||||
|
mContext = GBApplication.getContext();
|
||||||
|
final LineageWeatherManager weatherManager = LineageWeatherManager.getInstance(mContext);
|
||||||
|
if (weatherManager == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
|
|
||||||
|
String city = prefs.getString("weather_city", null);
|
||||||
|
String cityId = prefs.getString("weather_cityid", null);
|
||||||
|
|
||||||
|
if ((cityId == null || cityId.equals("")) && city != null && !city.equals("")) {
|
||||||
|
lookupCity(city);
|
||||||
|
} else if (city != null && cityId != null) {
|
||||||
|
weatherLocation = new WeatherLocation.Builder(cityId, city).build();
|
||||||
|
enablePeriodicAlarm(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lookupCity(String city) {
|
||||||
|
final LineageWeatherManager weatherManager = LineageWeatherManager.getInstance(mContext);
|
||||||
|
if (weatherManager == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (city != null && !city.equals("")) {
|
||||||
|
if (weatherManager.getActiveWeatherServiceProviderLabel() != null) {
|
||||||
|
weatherManager.lookupCity(city, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enablePeriodicAlarm(boolean enable) {
|
||||||
|
if ((mPendingIntent != null && enable) || (mPendingIntent == null && !enable)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AlarmManager am = (AlarmManager) (mContext.getSystemService(Context.ALARM_SERVICE));
|
||||||
|
if (am == null) {
|
||||||
|
LOG.warn("could not get alarm manager!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
Intent intent = new Intent("GB_UPDATE_WEATHER");
|
||||||
|
intent.setPackage(BuildConfig.APPLICATION_ID);
|
||||||
|
mPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
|
||||||
|
am.setInexactRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis() + 10000, AlarmManager.INTERVAL_HOUR, mPendingIntent);
|
||||||
|
} else {
|
||||||
|
am.cancel(mPendingIntent);
|
||||||
|
mPendingIntent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
|
|
||||||
|
String city = prefs.getString("weather_city", null);
|
||||||
|
String cityId = prefs.getString("weather_cityid", null);
|
||||||
|
|
||||||
|
if (city != null && !city.equals("") && cityId == null) {
|
||||||
|
lookupCity(city);
|
||||||
|
} else {
|
||||||
|
requestWeather();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestWeather() {
|
||||||
|
final LineageWeatherManager weatherManager = LineageWeatherManager.getInstance(GBApplication.getContext());
|
||||||
|
if (weatherManager.getActiveWeatherServiceProviderLabel() != null && weatherLocation != null) {
|
||||||
|
weatherManager.requestWeatherUpdate(weatherLocation, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWeatherRequestCompleted(int status, WeatherInfo weatherInfo) {
|
||||||
|
if (weatherInfo != null) {
|
||||||
|
LOG.info("weather: " + weatherInfo.toString());
|
||||||
|
WeatherSpec weatherSpec = new WeatherSpec();
|
||||||
|
weatherSpec.timestamp = (int) (weatherInfo.getTimestamp() / 1000);
|
||||||
|
weatherSpec.location = weatherInfo.getCity();
|
||||||
|
|
||||||
|
if (weatherInfo.getTemperatureUnit() == FAHRENHEIT) {
|
||||||
|
weatherSpec.currentTemp = (int) WeatherUtils.fahrenheitToCelsius(weatherInfo.getTemperature()) + 273;
|
||||||
|
weatherSpec.todayMaxTemp = (int) WeatherUtils.fahrenheitToCelsius(weatherInfo.getTodaysHigh()) + 273;
|
||||||
|
weatherSpec.todayMinTemp = (int) WeatherUtils.fahrenheitToCelsius(weatherInfo.getTodaysLow()) + 273;
|
||||||
|
} else {
|
||||||
|
weatherSpec.currentTemp = (int) weatherInfo.getTemperature() + 273;
|
||||||
|
weatherSpec.todayMaxTemp = (int) weatherInfo.getTodaysHigh() + 273;
|
||||||
|
weatherSpec.todayMinTemp = (int) weatherInfo.getTodaysLow() + 273;
|
||||||
|
}
|
||||||
|
if (weatherInfo.getWindSpeedUnit() == MPH) {
|
||||||
|
weatherSpec.windSpeed = (float) weatherInfo.getWindSpeed() * 1.609344f;
|
||||||
|
} else {
|
||||||
|
weatherSpec.windSpeed = (float) weatherInfo.getWindSpeed();
|
||||||
|
}
|
||||||
|
weatherSpec.windDirection = (int) weatherInfo.getWindDirection();
|
||||||
|
|
||||||
|
weatherSpec.currentConditionCode = Weather.mapToOpenWeatherMapCondition(LineageOstoYahooCondintion(weatherInfo.getConditionCode()));
|
||||||
|
weatherSpec.currentCondition = Weather.getConditionString(weatherSpec.currentConditionCode);
|
||||||
|
weatherSpec.currentHumidity = (int) weatherInfo.getHumidity();
|
||||||
|
|
||||||
|
weatherSpec.forecasts = new ArrayList<>();
|
||||||
|
List<WeatherInfo.DayForecast> forecasts = weatherInfo.getForecasts();
|
||||||
|
for (int i = 1; i < forecasts.size(); i++) {
|
||||||
|
WeatherInfo.DayForecast cmForecast = forecasts.get(i);
|
||||||
|
WeatherSpec.Forecast gbForecast = new WeatherSpec.Forecast();
|
||||||
|
if (weatherInfo.getTemperatureUnit() == FAHRENHEIT) {
|
||||||
|
gbForecast.maxTemp = (int) WeatherUtils.fahrenheitToCelsius(cmForecast.getHigh()) + 273;
|
||||||
|
gbForecast.minTemp = (int) WeatherUtils.fahrenheitToCelsius(cmForecast.getLow()) + 273;
|
||||||
|
} else {
|
||||||
|
gbForecast.maxTemp = (int) cmForecast.getHigh() + 273;
|
||||||
|
gbForecast.minTemp = (int) cmForecast.getLow() + 273;
|
||||||
|
}
|
||||||
|
gbForecast.conditionCode = Weather.mapToOpenWeatherMapCondition(LineageOstoYahooCondintion(cmForecast.getConditionCode()));
|
||||||
|
weatherSpec.forecasts.add(gbForecast);
|
||||||
|
}
|
||||||
|
Weather.getInstance().setWeatherSpec(weatherSpec);
|
||||||
|
GBApplication.deviceService().onSendWeather(weatherSpec);
|
||||||
|
} else {
|
||||||
|
LOG.info("request has returned null for WeatherInfo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cmCondition
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private int LineageOstoYahooCondintion(int cmCondition) {
|
||||||
|
int yahooCondition;
|
||||||
|
if (cmCondition <= SHOWERS) {
|
||||||
|
yahooCondition = cmCondition;
|
||||||
|
} else if (cmCondition <= SCATTERED_THUNDERSTORMS) {
|
||||||
|
yahooCondition = cmCondition + 1;
|
||||||
|
} else if (cmCondition <= SCATTERED_SNOW_SHOWERS) {
|
||||||
|
yahooCondition = cmCondition + 2;
|
||||||
|
} else if (cmCondition <= ISOLATED_THUNDERSHOWERS) {
|
||||||
|
yahooCondition = cmCondition + 3;
|
||||||
|
} else {
|
||||||
|
yahooCondition = NOT_AVAILABLE;
|
||||||
|
}
|
||||||
|
return yahooCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLookupCityRequestCompleted(int result, List<WeatherLocation> list) {
|
||||||
|
if (list != null) {
|
||||||
|
weatherLocation = list.get(0);
|
||||||
|
String cityId = weatherLocation.getCityId();
|
||||||
|
String city = weatherLocation.getCity();
|
||||||
|
|
||||||
|
SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
|
||||||
|
editor.putString("weather_city", city).apply();
|
||||||
|
editor.putString("weather_cityid", cityId).apply();
|
||||||
|
enablePeriodicAlarm(true);
|
||||||
|
requestWeather();
|
||||||
|
} else {
|
||||||
|
enablePeriodicAlarm(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -27,13 +27,11 @@ import android.content.BroadcastReceiver;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.media.MediaMetadata;
|
import android.media.MediaMetadata;
|
||||||
import android.media.session.PlaybackState;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
@ -44,6 +42,12 @@ import android.support.v4.media.session.MediaControllerCompat;
|
|||||||
import android.support.v4.media.session.MediaSessionCompat;
|
import android.support.v4.media.session.MediaSessionCompat;
|
||||||
import android.support.v4.media.session.PlaybackStateCompat;
|
import android.support.v4.media.session.PlaybackStateCompat;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.core.app.RemoteInput;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
import androidx.palette.graphics.Palette;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -53,11 +57,6 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
import androidx.core.app.RemoteInput;
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
import androidx.palette.graphics.Palette;
|
|
||||||
import de.greenrobot.dao.query.Query;
|
import de.greenrobot.dao.query.Query;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
@ -65,11 +64,11 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor;
|
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilter;
|
import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilter;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntry;
|
import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntry;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntryDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntryDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.AppNotificationType;
|
import nodomain.freeyourgadget.gadgetbridge.model.AppNotificationType;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
@ -242,10 +241,12 @@ public class NotificationListener extends NotificationListenerService {
|
|||||||
public void onNotificationPosted(StatusBarNotification sbn) {
|
public void onNotificationPosted(StatusBarNotification sbn) {
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
|
|
||||||
|
if (GBApplication.isRunningLollipopOrLater()) {
|
||||||
if ("call".equals(sbn.getNotification().category) && prefs.getBoolean("notification_support_voip_calls", false)) {
|
if ("call".equals(sbn.getNotification().category) && prefs.getBoolean("notification_support_voip_calls", false)) {
|
||||||
handleCallNotification(sbn);
|
handleCallNotification(sbn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (shouldIgnore(sbn)) {
|
if (shouldIgnore(sbn)) {
|
||||||
LOG.info("Ignore notification");
|
LOG.info("Ignore notification");
|
||||||
return;
|
return;
|
||||||
@ -531,6 +532,9 @@ public class NotificationListener extends NotificationListenerService {
|
|||||||
Bundle extras = NotificationCompat.getExtras(notification);
|
Bundle extras = NotificationCompat.getExtras(notification);
|
||||||
|
|
||||||
//dumpExtras(extras);
|
//dumpExtras(extras);
|
||||||
|
if (extras == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE);
|
CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE);
|
||||||
if (title != null) {
|
if (title != null) {
|
||||||
@ -582,13 +586,13 @@ public class NotificationListener extends NotificationListenerService {
|
|||||||
stateSpec.repeat = 1;
|
stateSpec.repeat = 1;
|
||||||
stateSpec.shuffle = 1;
|
stateSpec.shuffle = 1;
|
||||||
switch (s.getState()) {
|
switch (s.getState()) {
|
||||||
case PlaybackState.STATE_PLAYING:
|
case PlaybackStateCompat.STATE_PLAYING:
|
||||||
stateSpec.state = MusicStateSpec.STATE_PLAYING;
|
stateSpec.state = MusicStateSpec.STATE_PLAYING;
|
||||||
break;
|
break;
|
||||||
case PlaybackState.STATE_STOPPED:
|
case PlaybackStateCompat.STATE_STOPPED:
|
||||||
stateSpec.state = MusicStateSpec.STATE_STOPPED;
|
stateSpec.state = MusicStateSpec.STATE_STOPPED;
|
||||||
break;
|
break;
|
||||||
case PlaybackState.STATE_PAUSED:
|
case PlaybackStateCompat.STATE_PAUSED:
|
||||||
stateSpec.state = MusicStateSpec.STATE_PAUSED;
|
stateSpec.state = MusicStateSpec.STATE_PAUSED;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -624,13 +628,16 @@ public class NotificationListener extends NotificationListenerService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNotificationRemoved(StatusBarNotification sbn) {
|
public void onNotificationRemoved(StatusBarNotification sbn) {
|
||||||
LOG.info("Notification removed: " + sbn.getPackageName() + ": " + sbn.getNotification().category);
|
LOG.info("Notification removed: " + sbn.getPackageName());
|
||||||
|
if (GBApplication.isRunningLollipopOrLater()) {
|
||||||
|
LOG.info("Notification removed: " + sbn.getPackageName() + ", category: " + sbn.getNotification().category);
|
||||||
if (Notification.CATEGORY_CALL.equals(sbn.getNotification().category) && activeCallPostTime == sbn.getPostTime()) {
|
if (Notification.CATEGORY_CALL.equals(sbn.getNotification().category) && activeCallPostTime == sbn.getPostTime()) {
|
||||||
activeCallPostTime = 0;
|
activeCallPostTime = 0;
|
||||||
CallSpec callSpec = new CallSpec();
|
CallSpec callSpec = new CallSpec();
|
||||||
callSpec.command = CallSpec.CALL_END;
|
callSpec.command = CallSpec.CALL_END;
|
||||||
GBApplication.deviceService().onSetCallState(callSpec);
|
GBApplication.deviceService().onSetCallState(callSpec);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// FIXME: DISABLED for now
|
// FIXME: DISABLED for now
|
||||||
/*
|
/*
|
||||||
if (shouldIgnore(sbn))
|
if (shouldIgnore(sbn))
|
||||||
|
@ -116,9 +116,9 @@ public class ActivityKind {
|
|||||||
case TYPE_NOT_MEASURED:
|
case TYPE_NOT_MEASURED:
|
||||||
return R.drawable.ic_activity_not_measured;
|
return R.drawable.ic_activity_not_measured;
|
||||||
case TYPE_LIGHT_SLEEP:
|
case TYPE_LIGHT_SLEEP:
|
||||||
return R.drawable.ic_activity_light_sleep;
|
return R.drawable.ic_activity_sleep;
|
||||||
case TYPE_DEEP_SLEEP:
|
case TYPE_DEEP_SLEEP:
|
||||||
return R.drawable.ic_activity_deep_sleep;
|
return R.drawable.ic_activity_sleep;
|
||||||
case TYPE_RUNNING:
|
case TYPE_RUNNING:
|
||||||
return R.drawable.ic_activity_running;
|
return R.drawable.ic_activity_running;
|
||||||
case TYPE_WALKING:
|
case TYPE_WALKING:
|
||||||
@ -128,7 +128,9 @@ public class ActivityKind {
|
|||||||
case TYPE_TREADMILL:
|
case TYPE_TREADMILL:
|
||||||
return R.drawable.ic_activity_walking;
|
return R.drawable.ic_activity_walking;
|
||||||
case TYPE_EXERCISE: // fall through
|
case TYPE_EXERCISE: // fall through
|
||||||
|
return R.drawable.ic_activity_exercise;
|
||||||
case TYPE_SWIMMING: // fall through
|
case TYPE_SWIMMING: // fall through
|
||||||
|
return R.drawable.ic_activity_swimming;
|
||||||
case TYPE_NOT_WORN: // fall through
|
case TYPE_NOT_WORN: // fall through
|
||||||
case TYPE_ACTIVITY: // fall through
|
case TYPE_ACTIVITY: // fall through
|
||||||
case TYPE_UNKNOWN: // fall through
|
case TYPE_UNKNOWN: // fall through
|
||||||
|
@ -0,0 +1,149 @@
|
|||||||
|
/* Copyright (C) 2017-2019 Carsten Pfeiffer, Daniele Gobbetti
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityAnalysis;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
|
||||||
|
public class DailyTotals {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(DailyTotals.class);
|
||||||
|
|
||||||
|
|
||||||
|
public int[] getDailyTotalsForAllDevices(Calendar day) {
|
||||||
|
Context context = GBApplication.getContext();
|
||||||
|
//get today's steps for all devices in GB
|
||||||
|
int all_steps = 0;
|
||||||
|
int all_sleep = 0;
|
||||||
|
|
||||||
|
|
||||||
|
if (context instanceof GBApplication) {
|
||||||
|
GBApplication gbApp = (GBApplication) context;
|
||||||
|
List<? extends GBDevice> devices = gbApp.getDeviceManager().getDevices();
|
||||||
|
for (GBDevice device : devices) {
|
||||||
|
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||||
|
if (!coordinator.supportsActivityDataFetching()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int[] all_daily = getDailyTotalsForDevice(device, day);
|
||||||
|
all_steps += all_daily[0];
|
||||||
|
all_sleep += all_daily[1] + all_daily[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.debug("gbwidget daily totals, all steps:" + all_steps);
|
||||||
|
LOG.debug("gbwidget daily totals, all sleep:" + all_sleep);
|
||||||
|
return new int[]{all_steps, all_sleep};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int[] getDailyTotalsForDevice(GBDevice device, Calendar day) {
|
||||||
|
|
||||||
|
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||||
|
ActivityAnalysis analysis = new ActivityAnalysis();
|
||||||
|
ActivityAmounts amountsSteps;
|
||||||
|
ActivityAmounts amountsSleep;
|
||||||
|
|
||||||
|
amountsSteps = analysis.calculateActivityAmounts(getSamplesOfDay(handler, day, 0, device));
|
||||||
|
amountsSleep = analysis.calculateActivityAmounts(getSamplesOfDay(handler, day, -12, device));
|
||||||
|
|
||||||
|
int[] Sleep = getTotalsSleepForActivityAmounts(amountsSleep);
|
||||||
|
int Steps = getTotalsStepsForActivityAmounts(amountsSteps);
|
||||||
|
|
||||||
|
return new int[]{Steps, Sleep[0], Sleep[1]};
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
GB.toast("Error loading activity summaries.", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
|
return new int[]{0, 0, 0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] getTotalsSleepForActivityAmounts(ActivityAmounts activityAmounts) {
|
||||||
|
long totalSecondsDeepSleep = 0;
|
||||||
|
long totalSecondsLightSleep = 0;
|
||||||
|
for (ActivityAmount amount : activityAmounts.getAmounts()) {
|
||||||
|
if (amount.getActivityKind() == ActivityKind.TYPE_DEEP_SLEEP) {
|
||||||
|
totalSecondsDeepSleep += amount.getTotalSeconds();
|
||||||
|
} else if (amount.getActivityKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
|
||||||
|
totalSecondsLightSleep += amount.getTotalSeconds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int totalMinutesDeepSleep = (int) (totalSecondsDeepSleep / 60);
|
||||||
|
int totalMinutesLightSleep = (int) (totalSecondsLightSleep / 60);
|
||||||
|
return new int[]{totalMinutesDeepSleep, totalMinutesLightSleep};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int getTotalsStepsForActivityAmounts(ActivityAmounts activityAmounts) {
|
||||||
|
int totalSteps = 0;
|
||||||
|
|
||||||
|
for (ActivityAmount amount : activityAmounts.getAmounts()) {
|
||||||
|
totalSteps += amount.getTotalSteps();
|
||||||
|
}
|
||||||
|
return totalSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private List<? extends ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, int offsetHours, GBDevice device) {
|
||||||
|
int startTs;
|
||||||
|
int endTs;
|
||||||
|
|
||||||
|
day = (Calendar) day.clone(); // do not modify the caller's argument
|
||||||
|
day.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
day.set(Calendar.MINUTE, 0);
|
||||||
|
day.set(Calendar.SECOND, 0);
|
||||||
|
day.add(Calendar.HOUR, offsetHours);
|
||||||
|
|
||||||
|
startTs = (int) (day.getTimeInMillis() / 1000);
|
||||||
|
endTs = startTs + 24 * 60 * 60 - 1;
|
||||||
|
|
||||||
|
return getSamples(db, device, startTs, endTs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||||
|
return getAllSamples(db, device, tsFrom, tsTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected SampleProvider<? extends AbstractActivitySample> getProvider(DBHandler db, GBDevice device) {
|
||||||
|
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||||
|
return coordinator.getSampleProvider(device, db.getDaoSession());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected List<? extends ActivitySample> getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||||
|
SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
|
||||||
|
return provider.getAllActivitySamples(tsFrom, tsTo);
|
||||||
|
}
|
||||||
|
}
|
@ -57,6 +57,7 @@ public enum DeviceType {
|
|||||||
CASIOGB6900(120, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogb6900),
|
CASIOGB6900(120, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogb6900),
|
||||||
MISCALE2(131, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_miscale2),
|
MISCALE2(131, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_miscale2),
|
||||||
BFH16(140, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_bfh16),
|
BFH16(140, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_bfh16),
|
||||||
|
MIJIA_LYWSD02(200, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled, R.string.devicetype_mijia_lywsd02),
|
||||||
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
|
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
|
||||||
|
|
||||||
private final int key;
|
private final int key;
|
||||||
|
@ -58,7 +58,6 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventLEDColor;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSleepMonitorResult;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
@ -151,8 +150,6 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
|
|||||||
handleGBDeviceEvent((GBDeviceEventVersionInfo) deviceEvent);
|
handleGBDeviceEvent((GBDeviceEventVersionInfo) deviceEvent);
|
||||||
} else if (deviceEvent instanceof GBDeviceEventAppInfo) {
|
} else if (deviceEvent instanceof GBDeviceEventAppInfo) {
|
||||||
handleGBDeviceEvent((GBDeviceEventAppInfo) deviceEvent);
|
handleGBDeviceEvent((GBDeviceEventAppInfo) deviceEvent);
|
||||||
} else if (deviceEvent instanceof GBDeviceEventSleepMonitorResult) {
|
|
||||||
handleGBDeviceEvent((GBDeviceEventSleepMonitorResult) deviceEvent);
|
|
||||||
} else if (deviceEvent instanceof GBDeviceEventScreenshot) {
|
} else if (deviceEvent instanceof GBDeviceEventScreenshot) {
|
||||||
handleGBDeviceEvent((GBDeviceEventScreenshot) deviceEvent);
|
handleGBDeviceEvent((GBDeviceEventScreenshot) deviceEvent);
|
||||||
} else if (deviceEvent instanceof GBDeviceEventNotificationControl) {
|
} else if (deviceEvent instanceof GBDeviceEventNotificationControl) {
|
||||||
@ -258,18 +255,6 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
|
|||||||
LocalBroadcastManager.getInstance(context).sendBroadcast(appInfoIntent);
|
LocalBroadcastManager.getInstance(context).sendBroadcast(appInfoIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleGBDeviceEvent(GBDeviceEventSleepMonitorResult sleepMonitorResult) {
|
|
||||||
Context context = getContext();
|
|
||||||
LOG.info("Got event for SLEEP_MONIOR_RES");
|
|
||||||
Intent sleepMonitorIntent = new Intent(ChartsHost.REFRESH);
|
|
||||||
sleepMonitorIntent.putExtra("smartalarm_from", sleepMonitorResult.smartalarm_from);
|
|
||||||
sleepMonitorIntent.putExtra("smartalarm_to", sleepMonitorResult.smartalarm_to);
|
|
||||||
sleepMonitorIntent.putExtra("recording_base_timestamp", sleepMonitorResult.recording_base_timestamp);
|
|
||||||
sleepMonitorIntent.putExtra("alarm_gone_off", sleepMonitorResult.alarm_gone_off);
|
|
||||||
|
|
||||||
LocalBroadcastManager.getInstance(context).sendBroadcast(sleepMonitorIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleGBDeviceEvent(GBDeviceEventScreenshot screenshot) {
|
private void handleGBDeviceEvent(GBDeviceEventScreenshot screenshot) {
|
||||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-hhmmss", Locale.US);
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-hhmmss", Locale.US);
|
||||||
String filename = "screenshot_" + dateFormat.format(new Date()) + ".bmp";
|
String filename = "screenshot_" + dateFormat.format(new Date()) + ".bmp";
|
||||||
@ -409,4 +394,8 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
|
|||||||
|
|
||||||
LocalBroadcastManager.getInstance(context).sendBroadcast(messageIntent);
|
LocalBroadcastManager.getInstance(context).sendBroadcast(messageIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String customStringFilter(String inputString) {
|
||||||
|
return inputString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,10 @@ import android.os.Handler;
|
|||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -43,19 +47,18 @@ import java.util.ArrayList;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmClockReceiver;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmClockReceiver;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmReceiver;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmReceiver;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothPairingRequestReceiver;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothPairingRequestReceiver;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.CMWeatherReceiver;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.CMWeatherReceiver;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.LineageOsWeatherReceiver;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.MusicPlaybackReceiver;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.MusicPlaybackReceiver;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.OmniJawsObserver;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.OmniJawsObserver;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.PebbleReceiver;
|
import nodomain.freeyourgadget.gadgetbridge.externalevents.PebbleReceiver;
|
||||||
@ -78,6 +81,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||||
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ADD_CALENDAREVENT;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ADD_CALENDAREVENT;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_CONFIGURE;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_CONFIGURE;
|
||||||
@ -96,12 +100,12 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FI
|
|||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_HEARTRATE_TEST;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_HEARTRATE_TEST;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_INSTALL;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_INSTALL;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_READ_CONFIGURATION;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_APPINFO;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_APPINFO;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_DEVICEINFO;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_DEVICEINFO;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_SCREENSHOT;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_SCREENSHOT;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_RESET;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_RESET;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SEND_CONFIGURATION;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SEND_CONFIGURATION;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_READ_CONFIGURATION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SEND_WEATHER;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SEND_WEATHER;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETCANNEDMESSAGES;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETCANNEDMESSAGES;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICINFO;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICINFO;
|
||||||
@ -193,8 +197,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
|||||||
private AlarmReceiver mAlarmReceiver = null;
|
private AlarmReceiver mAlarmReceiver = null;
|
||||||
private CalendarReceiver mCalendarReceiver = null;
|
private CalendarReceiver mCalendarReceiver = null;
|
||||||
private CMWeatherReceiver mCMWeatherReceiver = null;
|
private CMWeatherReceiver mCMWeatherReceiver = null;
|
||||||
|
private LineageOsWeatherReceiver mLineageOsWeatherReceiver = null;
|
||||||
private OmniJawsObserver mOmniJawsObserver = null;
|
private OmniJawsObserver mOmniJawsObserver = null;
|
||||||
private Random mRandom = new Random();
|
|
||||||
|
|
||||||
private final String[] mMusicActions = {
|
private final String[] mMusicActions = {
|
||||||
"com.android.music.metachanged",
|
"com.android.music.metachanged",
|
||||||
@ -365,8 +369,11 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
|||||||
if (text == null || text.length() == 0)
|
if (text == null || text.length() == 0)
|
||||||
return text;
|
return text;
|
||||||
|
|
||||||
if (!mCoordinator.supportsUnicodeEmojis())
|
text = mDeviceSupport.customStringFilter(text);
|
||||||
|
|
||||||
|
if (!mCoordinator.supportsUnicodeEmojis()) {
|
||||||
return EmojiConverter.convertUnicodeEmojiToAscii(text, getApplicationContext());
|
return EmojiConverter.convertUnicodeEmojiToAscii(text, getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
@ -733,10 +740,17 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
|||||||
mCMWeatherReceiver = new CMWeatherReceiver();
|
mCMWeatherReceiver = new CMWeatherReceiver();
|
||||||
registerReceiver(mCMWeatherReceiver, new IntentFilter("GB_UPDATE_WEATHER"));
|
registerReceiver(mCMWeatherReceiver, new IntentFilter("GB_UPDATE_WEATHER"));
|
||||||
}
|
}
|
||||||
|
if (GBApplication.isRunningOreoOrLater()) {
|
||||||
|
if (mLineageOsWeatherReceiver == null && coordinator != null && coordinator.supportsWeather()) {
|
||||||
|
|
||||||
|
mLineageOsWeatherReceiver = new LineageOsWeatherReceiver();
|
||||||
|
registerReceiver(mLineageOsWeatherReceiver, new IntentFilter("GB_UPDATE_WEATHER"));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (mOmniJawsObserver == null && coordinator != null && coordinator.supportsWeather()) {
|
if (mOmniJawsObserver == null && coordinator != null && coordinator.supportsWeather()) {
|
||||||
try {
|
try {
|
||||||
mOmniJawsObserver = new OmniJawsObserver(new Handler());
|
mOmniJawsObserver = new OmniJawsObserver(new Handler());
|
||||||
getContentResolver().registerContentObserver(mOmniJawsObserver.WEATHER_URI, true, mOmniJawsObserver);
|
getContentResolver().registerContentObserver(OmniJawsObserver.WEATHER_URI, true, mOmniJawsObserver);
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
//Nothing wrong, it just means we're not running on omnirom.
|
//Nothing wrong, it just means we're not running on omnirom.
|
||||||
}
|
}
|
||||||
@ -784,6 +798,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
|||||||
unregisterReceiver(mCMWeatherReceiver);
|
unregisterReceiver(mCMWeatherReceiver);
|
||||||
mCMWeatherReceiver = null;
|
mCMWeatherReceiver = null;
|
||||||
}
|
}
|
||||||
|
if (mLineageOsWeatherReceiver != null) {
|
||||||
|
unregisterReceiver(mLineageOsWeatherReceiver);
|
||||||
|
mLineageOsWeatherReceiver = null;
|
||||||
|
}
|
||||||
if (mOmniJawsObserver != null) {
|
if (mOmniJawsObserver != null) {
|
||||||
getContentResolver().unregisterContentObserver(mOmniJawsObserver);
|
getContentResolver().unregisterContentObserver(mOmniJawsObserver);
|
||||||
}
|
}
|
||||||
|
@ -130,4 +130,9 @@ public interface DeviceSupport extends EventHandler {
|
|||||||
* Returns the Android context to use, e.g. to look up resources.
|
* Returns the Android context to use, e.g. to look up resources.
|
||||||
*/
|
*/
|
||||||
Context getContext();
|
Context getContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* converts String in a device specific way, e.g. re-map characters for a custom font
|
||||||
|
*/
|
||||||
|
String customStringFilter(String inputString);
|
||||||
}
|
}
|
||||||
|
@ -30,19 +30,20 @@ import nodomain.freeyourgadget.gadgetbridge.GBException;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor2.AmazfitCor2Support;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4Support;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.BFH16DeviceSupport;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900.CasioGB6900DeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900.CasioGB6900DeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.hplus.HPlusSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.hplus.HPlusSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor.AmazfitCorSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor.AmazfitCorSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor2.AmazfitCor2Support;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3.MiBand3Support;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3.MiBand3Support;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4Support;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.id115.ID115Support;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.id115.ID115Support;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.BFH16DeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30Support;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30Support;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.mijia_lywsd02.MijiaLywsd02Support;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miscale2.MiScale2DeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.miscale2.MiScale2DeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.no1f1.No1F1Support;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.no1f1.No1F1Support;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
|
||||||
@ -58,13 +59,13 @@ public class DeviceSupportFactory {
|
|||||||
private final BluetoothAdapter mBtAdapter;
|
private final BluetoothAdapter mBtAdapter;
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
|
||||||
public DeviceSupportFactory(Context context) {
|
DeviceSupportFactory(Context context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
|
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized DeviceSupport createDeviceSupport(GBDevice device) throws GBException {
|
public synchronized DeviceSupport createDeviceSupport(GBDevice device) throws GBException {
|
||||||
DeviceSupport deviceSupport = null;
|
DeviceSupport deviceSupport;
|
||||||
String deviceAddress = device.getAddress();
|
String deviceAddress = device.getAddress();
|
||||||
int indexFirstColon = deviceAddress.indexOf(":");
|
int indexFirstColon = deviceAddress.indexOf(":");
|
||||||
if (indexFirstColon > 0) {
|
if (indexFirstColon > 0) {
|
||||||
@ -196,6 +197,8 @@ public class DeviceSupportFactory {
|
|||||||
case BFH16:
|
case BFH16:
|
||||||
deviceSupport = new ServiceDeviceSupport(new BFH16DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
deviceSupport = new ServiceDeviceSupport(new BFH16DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||||
break;
|
break;
|
||||||
|
case MIJIA_LYWSD02:
|
||||||
|
deviceSupport = new ServiceDeviceSupport(new MijiaLywsd02Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||||
}
|
}
|
||||||
if (deviceSupport != null) {
|
if (deviceSupport != null) {
|
||||||
deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
|
deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
|
||||||
|
@ -113,6 +113,11 @@ public class ServiceDeviceSupport implements DeviceSupport {
|
|||||||
return delegate.getContext();
|
return delegate.getContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String customStringFilter(String inputString) {
|
||||||
|
return delegate.customStringFilter(inputString);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean useAutoConnect() {
|
public boolean useAutoConnect() {
|
||||||
return delegate.useAutoConnect();
|
return delegate.useAutoConnect();
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip;
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -33,43 +33,35 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
public class ActivityDetailsParser {
|
public class HuamiActivityDetailsParser {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ActivityDetailsParser.class);
|
private static final Logger LOG = LoggerFactory.getLogger(HuamiActivityDetailsParser.class);
|
||||||
|
|
||||||
private static final byte TYPE_GPS = 0;
|
private static final byte TYPE_GPS = 0;
|
||||||
private static final byte TYPE_HR = 1;
|
private static final byte TYPE_HR = 1;
|
||||||
private static final byte TYPE_UNKNOWN2 = 2;
|
private static final byte TYPE_PAUSE = 2;
|
||||||
private static final byte TYPE_PAUSE = 3;
|
private static final byte TYPE_RESUME = 3;
|
||||||
private static final byte TYPE_SPEED4 = 4;
|
private static final byte TYPE_SPEED4 = 4;
|
||||||
private static final byte TYPE_SPEED5 = 5;
|
private static final byte TYPE_SPEED5 = 5;
|
||||||
private static final byte TYPE_GPS_SPEED6 = 6;
|
private static final byte TYPE_SPEED6 = 6;
|
||||||
|
private static final byte TYPE_SWIMMING = 8;
|
||||||
|
|
||||||
public static final BigDecimal HUAMI_TO_DECIMAL_DEGREES_DIVISOR = new BigDecimal(3000000.0);
|
private static final BigDecimal HUAMI_TO_DECIMAL_DEGREES_DIVISOR = new BigDecimal(3000000.0);
|
||||||
private final BaseActivitySummary summary;
|
|
||||||
private final ActivityTrack activityTrack;
|
private final ActivityTrack activityTrack;
|
||||||
// private final int version;
|
|
||||||
private final Date baseDate;
|
private final Date baseDate;
|
||||||
private long baseLongitude;
|
private long baseLongitude;
|
||||||
private long baseLatitude;
|
private long baseLatitude;
|
||||||
private int baseAltitude;
|
private int baseAltitude;
|
||||||
private ActivityPoint lastActivityPoint;
|
private ActivityPoint lastActivityPoint;
|
||||||
|
|
||||||
public boolean getSkipCounterByte() {
|
|
||||||
return skipCounterByte;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSkipCounterByte(boolean skipCounterByte) {
|
public void setSkipCounterByte(boolean skipCounterByte) {
|
||||||
this.skipCounterByte = skipCounterByte;
|
this.skipCounterByte = skipCounterByte;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean skipCounterByte;
|
private boolean skipCounterByte;
|
||||||
|
|
||||||
public ActivityDetailsParser(BaseActivitySummary summary) {
|
public HuamiActivityDetailsParser(BaseActivitySummary summary) {
|
||||||
this.summary = summary;
|
|
||||||
// this.version = version;
|
|
||||||
// this.baseDate = baseDate;
|
|
||||||
//
|
|
||||||
this.baseLongitude = summary.getBaseLongitude();
|
this.baseLongitude = summary.getBaseLongitude();
|
||||||
this.baseLatitude = summary.getBaseLatitude();
|
this.baseLatitude = summary.getBaseLatitude();
|
||||||
this.baseAltitude = summary.getBaseAltitude();
|
this.baseAltitude = summary.getBaseAltitude();
|
||||||
@ -109,21 +101,24 @@ public class ActivityDetailsParser {
|
|||||||
case TYPE_HR:
|
case TYPE_HR:
|
||||||
i += consumeHeartRate(bytes, i, totalTimeOffset);
|
i += consumeHeartRate(bytes, i, totalTimeOffset);
|
||||||
break;
|
break;
|
||||||
case TYPE_UNKNOWN2:
|
|
||||||
i += consumeUnknown2(bytes, i);
|
|
||||||
break;
|
|
||||||
case TYPE_PAUSE:
|
case TYPE_PAUSE:
|
||||||
i += consumePause(bytes, i);
|
i += consumePause(bytes, i);
|
||||||
break;
|
break;
|
||||||
|
case TYPE_RESUME:
|
||||||
|
i += consumeResume(bytes, i);
|
||||||
|
break;
|
||||||
case TYPE_SPEED4:
|
case TYPE_SPEED4:
|
||||||
i += consumeSpeed4(bytes, i);
|
i += consumeSpeed4(bytes, i);
|
||||||
break;
|
break;
|
||||||
case TYPE_SPEED5:
|
case TYPE_SPEED5:
|
||||||
i += consumeSpeed5(bytes, i);
|
i += consumeSpeed5(bytes, i);
|
||||||
break;
|
break;
|
||||||
case TYPE_GPS_SPEED6:
|
case TYPE_SPEED6:
|
||||||
i += consumeSpeed6(bytes, i);
|
i += consumeSpeed6(bytes, i);
|
||||||
break;
|
break;
|
||||||
|
case TYPE_SWIMMING:
|
||||||
|
i += consumeSwimming(bytes, i);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOG.warn("unknown packet type" + type);
|
LOG.warn("unknown packet type" + type);
|
||||||
i+=6;
|
i+=6;
|
||||||
@ -213,7 +208,6 @@ public class ActivityDetailsParser {
|
|||||||
|
|
||||||
if (v2 == 0 && v3 == 0 && v4 == 0 && v5 == 0 && v6 == 0) {
|
if (v2 == 0 && v3 == 0 && v4 == 0 && v5 == 0 && v6 == 0) {
|
||||||
// new version
|
// new version
|
||||||
// LOG.info("detected heart rate in 'new' version, where version is: " + summary.getVersion());
|
|
||||||
LOG.info("detected heart rate in 'new' version format");
|
LOG.info("detected heart rate in 'new' version format");
|
||||||
ActivityPoint ap = getActivityPointFor(timeOffsetSeconds);
|
ActivityPoint ap = getActivityPointFor(timeOffsetSeconds);
|
||||||
ap.setHeartRate(v1);
|
ap.setHeartRate(v1);
|
||||||
@ -270,23 +264,33 @@ public class ActivityDetailsParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int consumeUnknown2(byte[] bytes, int offset) {
|
private int consumePause(byte[] bytes, int offset) {
|
||||||
return 6; // just guessing...
|
LOG.debug("got pause packet: " + GB.hexdump(bytes, offset, 6));
|
||||||
|
return 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int consumePause(byte[] bytes, int i) {
|
private int consumeResume(byte[] bytes, int offset) {
|
||||||
return 6; // just guessing...
|
LOG.debug("got resume package: " + GB.hexdump(bytes, offset, 6));
|
||||||
|
return 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int consumeSpeed4(byte[] bytes, int offset) {
|
private int consumeSpeed4(byte[] bytes, int offset) {
|
||||||
|
LOG.debug("got packet type 4 (speed): " + GB.hexdump(bytes, offset, 6));
|
||||||
return 6;
|
return 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int consumeSpeed5(byte[] bytes, int offset) {
|
private int consumeSpeed5(byte[] bytes, int offset) {
|
||||||
|
LOG.debug("got packet type 5 (speed): " + GB.hexdump(bytes, offset, 6));
|
||||||
return 6;
|
return 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int consumeSpeed6(byte[] bytes, int offset) {
|
private int consumeSpeed6(byte[] bytes, int offset) {
|
||||||
|
LOG.debug("got packet type 6 (speed): " + GB.hexdump(bytes, offset, 6));
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int consumeSwimming(byte[] bytes, int offset) {
|
||||||
|
LOG.debug("got packet type 8 (swimming?): " + GB.hexdump(bytes, offset, 6));
|
||||||
return 6;
|
return 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,20 +14,20 @@
|
|||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip;
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||||
|
|
||||||
public enum BipActivityType {
|
public enum HuamiSportsActivityType {
|
||||||
Outdoor(1),
|
Outdoor(1),
|
||||||
Treadmill(2),
|
Treadmill(2),
|
||||||
Walking(3),
|
Walking(3),
|
||||||
Cycling(4),
|
Cycling(4),
|
||||||
Exercise(5);
|
Exercise(5),
|
||||||
|
Swimming(6);
|
||||||
private final int code;
|
private final int code;
|
||||||
|
|
||||||
BipActivityType(final int code) {
|
HuamiSportsActivityType(final int code) {
|
||||||
this.code = code;
|
this.code = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,20 +43,22 @@ public enum BipActivityType {
|
|||||||
return ActivityKind.TYPE_WALKING;
|
return ActivityKind.TYPE_WALKING;
|
||||||
case Exercise:
|
case Exercise:
|
||||||
return ActivityKind.TYPE_EXERCISE;
|
return ActivityKind.TYPE_EXERCISE;
|
||||||
|
case Swimming:
|
||||||
|
return ActivityKind.TYPE_SWIMMING;
|
||||||
}
|
}
|
||||||
throw new RuntimeException("Not mapped activity kind for: " + this);
|
throw new RuntimeException("Not mapped activity kind for: " + this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BipActivityType fromCode(int bipCode) {
|
public static HuamiSportsActivityType fromCode(int huamiCode) {
|
||||||
for (BipActivityType type : values()) {
|
for (HuamiSportsActivityType type : values()) {
|
||||||
if (type.code == bipCode) {
|
if (type.code == huamiCode) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new RuntimeException("No matching BipActivityType for code: " + bipCode);
|
throw new RuntimeException("No matching HuamiSportsActivityType for code: " + huamiCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BipActivityType fromActivityKind(int activityKind) {
|
public static HuamiSportsActivityType fromActivityKind(int activityKind) {
|
||||||
switch (activityKind) {
|
switch (activityKind) {
|
||||||
case ActivityKind.TYPE_RUNNING:
|
case ActivityKind.TYPE_RUNNING:
|
||||||
return Outdoor;
|
return Outdoor;
|
||||||
@ -68,6 +70,8 @@ public enum BipActivityType {
|
|||||||
return Walking;
|
return Walking;
|
||||||
case ActivityKind.TYPE_EXERCISE:
|
case ActivityKind.TYPE_EXERCISE:
|
||||||
return Exercise;
|
return Exercise;
|
||||||
|
case ActivityKind.TYPE_SWIMMING:
|
||||||
|
return Swimming;
|
||||||
}
|
}
|
||||||
throw new RuntimeException("No matching activity activityKind: " + activityKind);
|
throw new RuntimeException("No matching activity activityKind: " + activityKind);
|
||||||
}
|
}
|
@ -42,11 +42,13 @@ import java.util.GregorianCalendar;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.SimpleTimeZone;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import cyanogenmod.weather.util.WeatherUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
@ -66,6 +68,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiWeatherConditions;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2.MiBand2FWHelper;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2.MiBand2FWHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3Coordinator;
|
||||||
@ -95,6 +98,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||||
@ -123,23 +127,12 @@ import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
||||||
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_COLOUR;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_COUNT;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_DURATION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_ORIGINAL_COLOUR;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_COUNT;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_COUNT;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_DURATION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PAUSE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PROFILE;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PROFILE;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_COLOUR;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_COUNT;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_DURATION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_ORIGINAL_COLOUR;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_COUNT;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_COUNT;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_DURATION;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_PAUSE;
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_PROFILE;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_PROFILE;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefIntValue;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefIntValue;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefStringValue;
|
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefStringValue;
|
||||||
@ -301,6 +294,8 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), enable);
|
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), enable);
|
||||||
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO), enable);
|
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO), enable);
|
||||||
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT), enable);
|
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT), enable);
|
||||||
|
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIO), enable);
|
||||||
|
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIODATA), enable);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -325,23 +320,6 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a custom notification to the given transaction builder
|
|
||||||
* @param vibrationProfile specifies how and how often the Band shall vibrate.
|
|
||||||
* @param simpleNotification
|
|
||||||
* @param flashTimes
|
|
||||||
* @param flashColour
|
|
||||||
* @param originalColour
|
|
||||||
* @param flashDuration
|
|
||||||
* @param extraAction an extra action to be executed after every vibration and flash sequence. Allows to abort the repetition, for example.
|
|
||||||
* @param builder
|
|
||||||
*/
|
|
||||||
private HuamiSupport sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) {
|
|
||||||
getNotificationStrategy().sendCustomNotification(vibrationProfile, simpleNotification, flashTimes, flashColour, originalColour, flashDuration, extraAction, builder);
|
|
||||||
LOG.info("Sending notification to MiBand");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NotificationStrategy getNotificationStrategy() {
|
public NotificationStrategy getNotificationStrategy() {
|
||||||
String firmwareVersion = gbDevice.getFirmwareVersion();
|
String firmwareVersion = gbDevice.getFirmwareVersion();
|
||||||
if (firmwareVersion != null) {
|
if (firmwareVersion != null) {
|
||||||
@ -437,7 +415,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
int userid = alias.hashCode(); // hash from alias like mi1
|
int userid = alias.hashCode(); // hash from alias like mi1
|
||||||
|
|
||||||
// FIXME: Do encoding like in PebbleProtocol, this is ugly
|
// FIXME: Do encoding like in PebbleProtocol, this is ugly
|
||||||
byte bytes[] = new byte[]{
|
byte[] bytes = new byte[]{
|
||||||
HuamiService.COMMAND_SET_USERINFO,
|
HuamiService.COMMAND_SET_USERINFO,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@ -561,58 +539,26 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void performPreferredNotification(String task, String notificationOrigin, SimpleNotification simpleNotification, int alertLevel, BtLEAction extraAction) {
|
private void performPreferredNotification(String task, String notificationOrigin, SimpleNotification simpleNotification, int alertLevel, BtLEAction extraAction) {
|
||||||
try {
|
try {
|
||||||
TransactionBuilder builder = performInitialized(task);
|
TransactionBuilder builder = performInitialized(task);
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
int vibrateDuration = getPreferredVibrateDuration(notificationOrigin, prefs);
|
|
||||||
int vibratePause = getPreferredVibratePause(notificationOrigin, prefs);
|
|
||||||
short vibrateTimes = getPreferredVibrateCount(notificationOrigin, prefs);
|
short vibrateTimes = getPreferredVibrateCount(notificationOrigin, prefs);
|
||||||
VibrationProfile profile = getPreferredVibrateProfile(notificationOrigin, prefs, vibrateTimes);
|
VibrationProfile profile = getPreferredVibrateProfile(notificationOrigin, prefs, vibrateTimes);
|
||||||
profile.setAlertLevel(alertLevel);
|
profile.setAlertLevel(alertLevel);
|
||||||
|
|
||||||
int flashTimes = getPreferredFlashCount(notificationOrigin, prefs);
|
getNotificationStrategy().sendCustomNotification(profile, simpleNotification, 0, 0, 0, 0, extraAction, builder);
|
||||||
int flashColour = getPreferredFlashColour(notificationOrigin, prefs);
|
|
||||||
int originalColour = getPreferredOriginalColour(notificationOrigin, prefs);
|
|
||||||
int flashDuration = getPreferredFlashDuration(notificationOrigin, prefs);
|
|
||||||
|
|
||||||
sendCustomNotification(profile, simpleNotification, flashTimes, flashColour, originalColour, flashDuration, extraAction, builder);
|
|
||||||
|
|
||||||
// sendCustomNotification(vibrateDuration, vibrateTimes, vibratePause, flashTimes, flashColour, originalColour, flashDuration, builder);
|
|
||||||
builder.queue(getQueue());
|
builder.queue(getQueue());
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
LOG.error("Unable to send notification to MI device", ex);
|
LOG.error("Unable to send notification to device", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getPreferredFlashDuration(String notificationOrigin, Prefs prefs) {
|
|
||||||
return getNotificationPrefIntValue(FLASH_DURATION, notificationOrigin, prefs, DEFAULT_VALUE_FLASH_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getPreferredOriginalColour(String notificationOrigin, Prefs prefs) {
|
|
||||||
return getNotificationPrefIntValue(FLASH_ORIGINAL_COLOUR, notificationOrigin, prefs, DEFAULT_VALUE_FLASH_ORIGINAL_COLOUR);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getPreferredFlashColour(String notificationOrigin, Prefs prefs) {
|
|
||||||
return getNotificationPrefIntValue(FLASH_COLOUR, notificationOrigin, prefs, DEFAULT_VALUE_FLASH_COLOUR);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getPreferredFlashCount(String notificationOrigin, Prefs prefs) {
|
|
||||||
return getNotificationPrefIntValue(FLASH_COUNT, notificationOrigin, prefs, DEFAULT_VALUE_FLASH_COUNT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getPreferredVibratePause(String notificationOrigin, Prefs prefs) {
|
|
||||||
return getNotificationPrefIntValue(VIBRATION_PAUSE, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_PAUSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private short getPreferredVibrateCount(String notificationOrigin, Prefs prefs) {
|
private short getPreferredVibrateCount(String notificationOrigin, Prefs prefs) {
|
||||||
return (short) Math.min(Short.MAX_VALUE, getNotificationPrefIntValue(VIBRATION_COUNT, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_COUNT));
|
return (short) Math.min(Short.MAX_VALUE, getNotificationPrefIntValue(VIBRATION_COUNT, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_COUNT));
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getPreferredVibrateDuration(String notificationOrigin, Prefs prefs) {
|
|
||||||
return getNotificationPrefIntValue(VIBRATION_DURATION, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
private VibrationProfile getPreferredVibrateProfile(String notificationOrigin, Prefs prefs, short repeat) {
|
private VibrationProfile getPreferredVibrateProfile(String notificationOrigin, Prefs prefs, short repeat) {
|
||||||
String profileId = getNotificationPrefStringValue(VIBRATION_PROFILE, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_PROFILE);
|
String profileId = getNotificationPrefStringValue(VIBRATION_PROFILE, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_PROFILE);
|
||||||
return VibrationProfile.getProfile(profileId, repeat);
|
return VibrationProfile.getProfile(profileId, repeat);
|
||||||
@ -682,7 +628,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sendCalendarEvents(builder);
|
sendCalendarEvents(builder);
|
||||||
builder.queue(getQueue());
|
builder.queue(getQueue());
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
LOG.error("Unable to set time on MI device", ex);
|
LOG.error("Unable to set time on Huami device", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -701,17 +647,17 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
performPreferredNotification("incoming call", MiBandConst.ORIGIN_INCOMING_CALL, simpleNotification, HuamiService.ALERT_LEVEL_PHONE_CALL, abortAction);
|
performPreferredNotification("incoming call", MiBandConst.ORIGIN_INCOMING_CALL, simpleNotification, HuamiService.ALERT_LEVEL_PHONE_CALL, abortAction);
|
||||||
} else if ((callSpec.command == CallSpec.CALL_START) || (callSpec.command == CallSpec.CALL_END)) {
|
} else if ((callSpec.command == CallSpec.CALL_START) || (callSpec.command == CallSpec.CALL_END)) {
|
||||||
telephoneRinging = false;
|
telephoneRinging = false;
|
||||||
stopCurrentNotification();
|
stopCurrentCallNotification();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopCurrentNotification() {
|
private void stopCurrentCallNotification() {
|
||||||
try {
|
try {
|
||||||
TransactionBuilder builder = performInitialized("stop notification");
|
TransactionBuilder builder = performInitialized("stop notification");
|
||||||
getNotificationStrategy().stopCurrentNotification(builder);
|
getNotificationStrategy().stopCurrentNotification(builder);
|
||||||
builder.queue(getQueue());
|
builder.queue(getQueue());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Error stopping notification");
|
LOG.error("Error stopping call notification");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -760,9 +706,8 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void sendMusicStateToDevice() {
|
private void sendMusicStateToDevice() {
|
||||||
|
|
||||||
|
|
||||||
if (characteristicChunked == null) {
|
if (characteristicChunked == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -878,7 +823,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
builder.write(characteristicHRControlPoint, startHeartMeasurementManual);
|
builder.write(characteristicHRControlPoint, startHeartMeasurementManual);
|
||||||
builder.queue(getQueue());
|
builder.queue(getQueue());
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
LOG.error("Unable to read heart rate with MI2", ex);
|
LOG.error("Unable to read heart rate from Huami device", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -939,7 +884,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
try {
|
try {
|
||||||
new FetchActivityOperation(this).perform();
|
new FetchActivityOperation(this).perform();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
LOG.error("Unable to fetch MI activity data", ex);
|
LOG.error("Unable to fetch activity data", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -969,7 +914,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getLatency(int minConnectionInterval, int maxConnectionInterval, int latency, int timeout, int advertisementInterval) {
|
private byte[] getLatency(int minConnectionInterval, int maxConnectionInterval, int latency, int timeout, int advertisementInterval) {
|
||||||
byte result[] = new byte[12];
|
byte[] result = new byte[12];
|
||||||
result[0] = (byte) (minConnectionInterval & 0xff);
|
result[0] = (byte) (minConnectionInterval & 0xff);
|
||||||
result[1] = (byte) (0xff & minConnectionInterval >> 8);
|
result[1] = (byte) (0xff & minConnectionInterval >> 8);
|
||||||
result[2] = (byte) (maxConnectionInterval & 0xff);
|
result[2] = (byte) (maxConnectionInterval & 0xff);
|
||||||
@ -1557,9 +1502,15 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
case MiBandConst.PREF_SWIPE_UNLOCK:
|
case MiBandConst.PREF_SWIPE_UNLOCK:
|
||||||
setBandScreenUnlock(builder);
|
setBandScreenUnlock(builder);
|
||||||
break;
|
break;
|
||||||
case "dateformat":
|
case HuamiConst.PREF_DATEFORMAT:
|
||||||
setDateFormat(builder);
|
setDateFormat(builder);
|
||||||
break;
|
break;
|
||||||
|
case HuamiConst.PREF_LANGUAGE:
|
||||||
|
setLanguage(builder);
|
||||||
|
break;
|
||||||
|
case HuamiConst.PREF_EXPOSE_HR_THIRDPARTY:
|
||||||
|
setExposeHRThridParty(builder);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
builder.queue(getQueue());
|
builder.queue(getQueue());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -1579,7 +1530,190 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendWeather(WeatherSpec weatherSpec) {
|
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||||
|
// FIXME: currently HuamiSupport *is* MiBand2 support, so return if we are using Mi Band 2
|
||||||
|
if (gbDevice.getType() == DeviceType.MIBAND2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gbDevice.getFirmwareVersion() == null) {
|
||||||
|
LOG.warn("Device not initialized yet, so not sending weather info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean supportsConditionString = false;
|
||||||
|
|
||||||
|
Version version = new Version(gbDevice.getFirmwareVersion());
|
||||||
|
if (version.compareTo(new Version("0.0.8.74")) >= 0) {
|
||||||
|
supportsConditionString = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MiBandConst.DistanceUnit unit = HuamiCoordinator.getDistanceUnit();
|
||||||
|
int tz_offset_hours = SimpleTimeZone.getDefault().getOffset(weatherSpec.timestamp * 1000L) / (1000 * 60 * 60);
|
||||||
|
try {
|
||||||
|
TransactionBuilder builder;
|
||||||
|
builder = performInitialized("Sending current temp");
|
||||||
|
|
||||||
|
byte condition = HuamiWeatherConditions.mapToAmazfitBipWeatherCode(weatherSpec.currentConditionCode);
|
||||||
|
|
||||||
|
int length = 8;
|
||||||
|
if (supportsConditionString) {
|
||||||
|
length += weatherSpec.currentCondition.getBytes().length + 1;
|
||||||
|
}
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(length);
|
||||||
|
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
buf.put((byte) 2);
|
||||||
|
buf.putInt(weatherSpec.timestamp);
|
||||||
|
buf.put((byte) (tz_offset_hours * 4));
|
||||||
|
buf.put(condition);
|
||||||
|
|
||||||
|
int currentTemp = weatherSpec.currentTemp - 273;
|
||||||
|
if (unit == MiBandConst.DistanceUnit.IMPERIAL) {
|
||||||
|
currentTemp = (int) WeatherUtils.celsiusToFahrenheit(currentTemp);
|
||||||
|
}
|
||||||
|
buf.put((byte) currentTemp);
|
||||||
|
|
||||||
|
if (supportsConditionString) {
|
||||||
|
buf.put(weatherSpec.currentCondition.getBytes());
|
||||||
|
buf.put((byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (characteristicChunked != null) {
|
||||||
|
writeToChunked(builder, 1, buf.array());
|
||||||
|
} else {
|
||||||
|
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.queue(getQueue());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.error("Error sending current weather", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
TransactionBuilder builder;
|
||||||
|
builder = performInitialized("Sending air quality index");
|
||||||
|
int length = 8;
|
||||||
|
String aqiString = "(n/a)";
|
||||||
|
if (supportsConditionString) {
|
||||||
|
length += aqiString.getBytes().length + 1;
|
||||||
|
}
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(length);
|
||||||
|
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buf.put((byte) 4);
|
||||||
|
buf.putInt(weatherSpec.timestamp);
|
||||||
|
buf.put((byte) (tz_offset_hours * 4));
|
||||||
|
buf.putShort((short) 0);
|
||||||
|
if (supportsConditionString) {
|
||||||
|
buf.put(aqiString.getBytes());
|
||||||
|
buf.put((byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (characteristicChunked != null) {
|
||||||
|
writeToChunked(builder, 1, buf.array());
|
||||||
|
} else {
|
||||||
|
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.queue(getQueue());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOG.error("Error sending air quality");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
TransactionBuilder builder = performInitialized("Sending weather forecast");
|
||||||
|
|
||||||
|
final byte NR_DAYS = (byte) (1 + weatherSpec.forecasts.size());
|
||||||
|
int bytesPerDay = 4;
|
||||||
|
|
||||||
|
int conditionsLength = 0;
|
||||||
|
if (supportsConditionString) {
|
||||||
|
bytesPerDay = 5;
|
||||||
|
conditionsLength = weatherSpec.currentCondition.getBytes().length;
|
||||||
|
for (WeatherSpec.Forecast forecast : weatherSpec.forecasts) {
|
||||||
|
conditionsLength += Weather.getConditionString(forecast.conditionCode).getBytes().length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = 7 + bytesPerDay * NR_DAYS + conditionsLength;
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(length);
|
||||||
|
|
||||||
|
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buf.put((byte) 1);
|
||||||
|
buf.putInt(weatherSpec.timestamp);
|
||||||
|
buf.put((byte) (tz_offset_hours * 4));
|
||||||
|
|
||||||
|
buf.put(NR_DAYS);
|
||||||
|
|
||||||
|
byte condition = HuamiWeatherConditions.mapToAmazfitBipWeatherCode(weatherSpec.currentConditionCode);
|
||||||
|
buf.put(condition);
|
||||||
|
buf.put(condition);
|
||||||
|
|
||||||
|
int todayMaxTemp = weatherSpec.todayMaxTemp - 273;
|
||||||
|
int todayMinTemp = weatherSpec.todayMinTemp - 273;
|
||||||
|
if (unit == MiBandConst.DistanceUnit.IMPERIAL) {
|
||||||
|
todayMaxTemp = (int) WeatherUtils.celsiusToFahrenheit(todayMaxTemp);
|
||||||
|
todayMinTemp = (int) WeatherUtils.celsiusToFahrenheit(todayMinTemp);
|
||||||
|
}
|
||||||
|
buf.put((byte) todayMaxTemp);
|
||||||
|
buf.put((byte) todayMinTemp);
|
||||||
|
|
||||||
|
if (supportsConditionString) {
|
||||||
|
buf.put(weatherSpec.currentCondition.getBytes());
|
||||||
|
buf.put((byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (WeatherSpec.Forecast forecast : weatherSpec.forecasts) {
|
||||||
|
condition = HuamiWeatherConditions.mapToAmazfitBipWeatherCode(forecast.conditionCode);
|
||||||
|
buf.put(condition);
|
||||||
|
buf.put(condition);
|
||||||
|
|
||||||
|
int forecastMaxTemp = forecast.maxTemp - 273;
|
||||||
|
int forecastMinTemp = forecast.minTemp - 273;
|
||||||
|
if (unit == MiBandConst.DistanceUnit.IMPERIAL) {
|
||||||
|
forecastMaxTemp = (int) WeatherUtils.celsiusToFahrenheit(forecastMaxTemp);
|
||||||
|
forecastMinTemp = (int) WeatherUtils.celsiusToFahrenheit(forecastMinTemp);
|
||||||
|
}
|
||||||
|
buf.put((byte) forecastMaxTemp);
|
||||||
|
buf.put((byte) forecastMinTemp);
|
||||||
|
|
||||||
|
if (supportsConditionString) {
|
||||||
|
buf.put(Weather.getConditionString(forecast.conditionCode).getBytes());
|
||||||
|
buf.put((byte) 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (characteristicChunked != null) {
|
||||||
|
writeToChunked(builder, 1, buf.array());
|
||||||
|
} else {
|
||||||
|
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.queue(getQueue());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.error("Error sending weather forecast", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
TransactionBuilder builder;
|
||||||
|
builder = performInitialized("Sending forecast location");
|
||||||
|
|
||||||
|
int length = 2 + weatherSpec.location.getBytes().length;
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(length);
|
||||||
|
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
buf.put((byte) 8);
|
||||||
|
buf.put(weatherSpec.location.getBytes());
|
||||||
|
buf.put((byte) 0);
|
||||||
|
|
||||||
|
|
||||||
|
if (characteristicChunked != null) {
|
||||||
|
writeToChunked(builder, 1, buf.array());
|
||||||
|
} else {
|
||||||
|
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.queue(getQueue());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.error("Error sending current forecast location", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private HuamiSupport setDateDisplay(TransactionBuilder builder) {
|
private HuamiSupport setDateDisplay(TransactionBuilder builder) {
|
||||||
@ -1904,6 +2038,20 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private HuamiSupport setExposeHRThridParty(TransactionBuilder builder) {
|
||||||
|
boolean enable = HuamiCoordinator.getExposeHRThirdParty(gbDevice.getAddress());
|
||||||
|
LOG.info("Setting exposure of HR to third party apps to: " + enable);
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_ENBALE_HR_CONNECTION);
|
||||||
|
} else {
|
||||||
|
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_DISABLE_HR_CONNECTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
protected void writeToChunked(TransactionBuilder builder, int type, byte[] data) {
|
protected void writeToChunked(TransactionBuilder builder, int type, byte[] data) {
|
||||||
final int MAX_CHUNKLENGTH = 17;
|
final int MAX_CHUNKLENGTH = 17;
|
||||||
int remaining = data.length;
|
int remaining = data.length;
|
||||||
@ -1932,6 +2080,42 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String customStringFilter(String inputString) {
|
||||||
|
if (HuamiCoordinator.getUseCustomFont(gbDevice.getAddress())) {
|
||||||
|
return convertEmojiToCustomFont(inputString);
|
||||||
|
}
|
||||||
|
return inputString;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String convertEmojiToCustomFont(String str) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int i = 0;
|
||||||
|
while (i < str.length()) {
|
||||||
|
char charAt = str.charAt(i);
|
||||||
|
if (Character.isHighSurrogate(charAt)) {
|
||||||
|
int i2 = i + 1;
|
||||||
|
try {
|
||||||
|
int codePoint = Character.toCodePoint(charAt, str.charAt(i2));
|
||||||
|
if (codePoint < 127744 || codePoint > 129510) {
|
||||||
|
sb.append(charAt);
|
||||||
|
} else {
|
||||||
|
sb.append((char) (codePoint - 83712));
|
||||||
|
i = i2;
|
||||||
|
}
|
||||||
|
} catch (StringIndexOutOfBoundsException e) {
|
||||||
|
LOG.warn("error while converting emoji to custom font", e);
|
||||||
|
sb.append(charAt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.append(charAt);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public void phase2Initialize(TransactionBuilder builder) {
|
public void phase2Initialize(TransactionBuilder builder) {
|
||||||
LOG.info("phase2Initialize...");
|
LOG.info("phase2Initialize...");
|
||||||
requestBatteryInfo(builder);
|
requestBatteryInfo(builder);
|
||||||
@ -1954,6 +2138,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
setInactivityWarnings(builder);
|
setInactivityWarnings(builder);
|
||||||
setHeartrateSleepSupport(builder);
|
setHeartrateSleepSupport(builder);
|
||||||
setDisconnectNotification(builder);
|
setDisconnectNotification(builder);
|
||||||
|
setExposeHRThridParty(builder);
|
||||||
setHeartrateMeasurementInterval(builder, getHeartRateMeasurementInterval());
|
setHeartrateMeasurementInterval(builder, getHeartRateMeasurementInterval());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +107,11 @@ public class AmazfitBipFirmwareInfo extends HuamiFirmwareInfo {
|
|||||||
crcToVersion.put(60002, "1.1.5.04");
|
crcToVersion.put(60002, "1.1.5.04");
|
||||||
crcToVersion.put(5229, "1.1.5.12");
|
crcToVersion.put(5229, "1.1.5.12");
|
||||||
crcToVersion.put(32576, "1.1.5.16");
|
crcToVersion.put(32576, "1.1.5.16");
|
||||||
|
crcToVersion.put(28893, "1.1.5.24");
|
||||||
|
crcToVersion.put(61710, "1.1.5.56");
|
||||||
|
|
||||||
|
// Latin Firmware
|
||||||
|
crcToVersion.put(52828, "1.1.5.36 (Latin)");
|
||||||
|
|
||||||
// resources
|
// resources
|
||||||
crcToVersion.put(12586, "0.0.8.74");
|
crcToVersion.put(12586, "0.0.8.74");
|
||||||
@ -130,7 +135,9 @@ public class AmazfitBipFirmwareInfo extends HuamiFirmwareInfo {
|
|||||||
crcToVersion.put(23073, "0.1.1.45");
|
crcToVersion.put(23073, "0.1.1.45");
|
||||||
crcToVersion.put(59245, "1.0.2.00");
|
crcToVersion.put(59245, "1.0.2.00");
|
||||||
crcToVersion.put(20591, "1.1.2.05");
|
crcToVersion.put(20591, "1.1.2.05");
|
||||||
crcToVersion.put(5341, "1.1.5.02-16");
|
crcToVersion.put(5341, "1.1.5.02-24");
|
||||||
|
crcToVersion.put(22662, "1.1.5.36");
|
||||||
|
crcToVersion.put(24045, "1.1.5.56");
|
||||||
|
|
||||||
// gps
|
// gps
|
||||||
crcToVersion.put(61520, "9367,8f79a91,0,0,");
|
crcToVersion.put(61520, "9367,8f79a91,0,0,");
|
||||||
|
@ -28,43 +28,30 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.SimpleTimeZone;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import cyanogenmod.weather.util.WeatherUtils;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiWeatherConditions;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipFWHelper;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipFWHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ConditionalWriteAction;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiIcon;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiIcon;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.operations.AmazfitBipFetchLogsOperation;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchActivityOperation;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchActivityOperation;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSportsSummaryOperation;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSportsSummaryOperation;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.HuamiFetchDebugLogsOperation;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
|
||||||
|
|
||||||
public class AmazfitBipSupport extends HuamiSupport {
|
public class AmazfitBipSupport extends HuamiSupport {
|
||||||
|
|
||||||
@ -252,187 +239,6 @@ public class AmazfitBipSupport extends HuamiSupport {
|
|||||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), command);
|
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), command);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSendWeather(WeatherSpec weatherSpec) {
|
|
||||||
if (gbDevice.getFirmwareVersion() == null) {
|
|
||||||
LOG.warn("Device not initialized yet, so not sending weather info");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
boolean supportsConditionString = false;
|
|
||||||
|
|
||||||
Version version = new Version(gbDevice.getFirmwareVersion());
|
|
||||||
if (version.compareTo(new Version("0.0.8.74")) >= 0) {
|
|
||||||
supportsConditionString = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
MiBandConst.DistanceUnit unit = HuamiCoordinator.getDistanceUnit();
|
|
||||||
int tz_offset_hours = SimpleTimeZone.getDefault().getOffset(weatherSpec.timestamp * 1000L) / (1000 * 60 * 60);
|
|
||||||
try {
|
|
||||||
TransactionBuilder builder;
|
|
||||||
builder = performInitialized("Sending current temp");
|
|
||||||
|
|
||||||
byte condition = HuamiWeatherConditions.mapToAmazfitBipWeatherCode(weatherSpec.currentConditionCode);
|
|
||||||
|
|
||||||
int length = 8;
|
|
||||||
if (supportsConditionString) {
|
|
||||||
length += weatherSpec.currentCondition.getBytes().length + 1;
|
|
||||||
}
|
|
||||||
ByteBuffer buf = ByteBuffer.allocate(length);
|
|
||||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
|
|
||||||
buf.put((byte) 2);
|
|
||||||
buf.putInt(weatherSpec.timestamp);
|
|
||||||
buf.put((byte) (tz_offset_hours * 4));
|
|
||||||
buf.put(condition);
|
|
||||||
|
|
||||||
int currentTemp = weatherSpec.currentTemp - 273;
|
|
||||||
if (unit == MiBandConst.DistanceUnit.IMPERIAL) {
|
|
||||||
currentTemp = (int) WeatherUtils.celsiusToFahrenheit(currentTemp);
|
|
||||||
}
|
|
||||||
buf.put((byte) currentTemp);
|
|
||||||
|
|
||||||
if (supportsConditionString) {
|
|
||||||
buf.put(weatherSpec.currentCondition.getBytes());
|
|
||||||
buf.put((byte) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (characteristicChunked != null) {
|
|
||||||
writeToChunked(builder, 1, buf.array());
|
|
||||||
} else {
|
|
||||||
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.queue(getQueue());
|
|
||||||
} catch (Exception ex) {
|
|
||||||
LOG.error("Error sending current weather", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gbDevice.getType() != DeviceType.AMAZFITCOR) {
|
|
||||||
try {
|
|
||||||
TransactionBuilder builder;
|
|
||||||
builder = performInitialized("Sending air quality index");
|
|
||||||
int length = 8;
|
|
||||||
String aqiString = "(n/a)";
|
|
||||||
if (supportsConditionString) {
|
|
||||||
length += aqiString.getBytes().length + 1;
|
|
||||||
}
|
|
||||||
ByteBuffer buf = ByteBuffer.allocate(length);
|
|
||||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
buf.put((byte) 4);
|
|
||||||
buf.putInt(weatherSpec.timestamp);
|
|
||||||
buf.put((byte) (tz_offset_hours * 4));
|
|
||||||
buf.putShort((short) 0);
|
|
||||||
if (supportsConditionString) {
|
|
||||||
buf.put(aqiString.getBytes());
|
|
||||||
buf.put((byte) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (characteristicChunked != null) {
|
|
||||||
writeToChunked(builder, 1, buf.array());
|
|
||||||
} else {
|
|
||||||
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.queue(getQueue());
|
|
||||||
} catch (IOException ex) {
|
|
||||||
LOG.error("Error sending air quality");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
TransactionBuilder builder = performInitialized("Sending weather forecast");
|
|
||||||
|
|
||||||
final byte NR_DAYS = (byte) (1 + weatherSpec.forecasts.size());
|
|
||||||
int bytesPerDay = 4;
|
|
||||||
|
|
||||||
int conditionsLength = 0;
|
|
||||||
if (supportsConditionString) {
|
|
||||||
bytesPerDay = 5;
|
|
||||||
conditionsLength = weatherSpec.currentCondition.getBytes().length;
|
|
||||||
for (WeatherSpec.Forecast forecast : weatherSpec.forecasts) {
|
|
||||||
conditionsLength += Weather.getConditionString(forecast.conditionCode).getBytes().length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int length = 7 + bytesPerDay * NR_DAYS + conditionsLength;
|
|
||||||
ByteBuffer buf = ByteBuffer.allocate(length);
|
|
||||||
|
|
||||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
buf.put((byte) 1);
|
|
||||||
buf.putInt(weatherSpec.timestamp);
|
|
||||||
buf.put((byte) (tz_offset_hours * 4));
|
|
||||||
|
|
||||||
buf.put(NR_DAYS);
|
|
||||||
|
|
||||||
byte condition = HuamiWeatherConditions.mapToAmazfitBipWeatherCode(weatherSpec.currentConditionCode);
|
|
||||||
buf.put(condition);
|
|
||||||
buf.put(condition);
|
|
||||||
|
|
||||||
int todayMaxTemp = weatherSpec.todayMaxTemp - 273;
|
|
||||||
int todayMinTemp = weatherSpec.todayMinTemp - 273;
|
|
||||||
if (unit == MiBandConst.DistanceUnit.IMPERIAL) {
|
|
||||||
todayMaxTemp = (int) WeatherUtils.celsiusToFahrenheit(todayMaxTemp);
|
|
||||||
todayMinTemp = (int) WeatherUtils.celsiusToFahrenheit(todayMinTemp);
|
|
||||||
}
|
|
||||||
buf.put((byte) todayMaxTemp);
|
|
||||||
buf.put((byte) todayMinTemp);
|
|
||||||
|
|
||||||
if (supportsConditionString) {
|
|
||||||
buf.put(weatherSpec.currentCondition.getBytes());
|
|
||||||
buf.put((byte) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (WeatherSpec.Forecast forecast : weatherSpec.forecasts) {
|
|
||||||
condition = HuamiWeatherConditions.mapToAmazfitBipWeatherCode(forecast.conditionCode);
|
|
||||||
buf.put(condition);
|
|
||||||
buf.put(condition);
|
|
||||||
|
|
||||||
int forecastMaxTemp = forecast.maxTemp - 273;
|
|
||||||
int forecastMinTemp = forecast.minTemp - 273;
|
|
||||||
if (unit == MiBandConst.DistanceUnit.IMPERIAL) {
|
|
||||||
forecastMaxTemp = (int) WeatherUtils.celsiusToFahrenheit(forecastMaxTemp);
|
|
||||||
forecastMinTemp = (int) WeatherUtils.celsiusToFahrenheit(forecastMinTemp);
|
|
||||||
}
|
|
||||||
buf.put((byte) forecastMaxTemp);
|
|
||||||
buf.put((byte) forecastMinTemp);
|
|
||||||
|
|
||||||
if (supportsConditionString) {
|
|
||||||
buf.put(Weather.getConditionString(forecast.conditionCode).getBytes());
|
|
||||||
buf.put((byte) 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (characteristicChunked != null) {
|
|
||||||
writeToChunked(builder, 1, buf.array());
|
|
||||||
} else {
|
|
||||||
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.queue(getQueue());
|
|
||||||
} catch (Exception ex) {
|
|
||||||
LOG.error("Error sending weather forecast", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gbDevice.getType() == DeviceType.AMAZFITCOR) {
|
|
||||||
try {
|
|
||||||
TransactionBuilder builder;
|
|
||||||
builder = performInitialized("Sending forecast location");
|
|
||||||
|
|
||||||
int length = 2 + weatherSpec.location.getBytes().length;
|
|
||||||
ByteBuffer buf = ByteBuffer.allocate(length);
|
|
||||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
buf.put((byte) 8);
|
|
||||||
buf.put(weatherSpec.location.getBytes());
|
|
||||||
buf.put((byte) 0);
|
|
||||||
|
|
||||||
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
|
|
||||||
builder.queue(getQueue());
|
|
||||||
} catch (Exception ex) {
|
|
||||||
LOG.error("Error sending current forecast location", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFetchRecordedData(int dataTypes) {
|
public void onFetchRecordedData(int dataTypes) {
|
||||||
try {
|
try {
|
||||||
@ -442,9 +248,8 @@ public class AmazfitBipSupport extends HuamiSupport {
|
|||||||
} else if (dataTypes == RecordedDataTypes.TYPE_GPS_TRACKS) {
|
} else if (dataTypes == RecordedDataTypes.TYPE_GPS_TRACKS) {
|
||||||
new FetchSportsSummaryOperation(this).perform();
|
new FetchSportsSummaryOperation(this).perform();
|
||||||
} else if (dataTypes == RecordedDataTypes.TYPE_DEBUGLOGS) {
|
} else if (dataTypes == RecordedDataTypes.TYPE_DEBUGLOGS) {
|
||||||
new AmazfitBipFetchLogsOperation(this).perform();
|
new HuamiFetchDebugLogsOperation(this).perform();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
LOG.warn("fetching multiple data types at once is not supported yet");
|
LOG.warn("fetching multiple data types at once is not supported yet");
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
|
@ -86,4 +86,10 @@ public class Mi2TextNotificationStrategy extends Mi2NotificationStrategy {
|
|||||||
NewAlert alert = new NewAlert(category, 1, simpleNotification.getMessage());
|
NewAlert alert = new NewAlert(category, 1, simpleNotification.getMessage());
|
||||||
profile.newAlert(builder, alert, OverflowStrategy.MAKE_MULTIPLE);
|
profile.newAlert(builder, alert, OverflowStrategy.MAKE_MULTIPLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopCurrentNotification(TransactionBuilder builder) {
|
||||||
|
BluetoothGattCharacteristic alert = getSupport().getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_NEW_ALERT);
|
||||||
|
builder.write(alert, new byte[]{(byte) AlertCategory.IncomingCall.getId(), 0});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,9 +86,13 @@ public class MiBand3Support extends AmazfitBipSupport {
|
|||||||
command[1] |= 0x80;
|
command[1] |= 0x80;
|
||||||
command[10] = pos++;
|
command[10] = pos++;
|
||||||
}
|
}
|
||||||
|
if (pages.contains("nfc")) {
|
||||||
|
command[2] |= 0x01;
|
||||||
|
command[11] = pos++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 4; i <= 10; i++) {
|
for (int i = 4; i <= 11; i++) {
|
||||||
if (command[i] == 0) {
|
if (command[i] == 0) {
|
||||||
command[i] = pos++;
|
command[i] = pos++;
|
||||||
}
|
}
|
||||||
|
@ -38,9 +38,13 @@ public class MiBand4FirmwareInfo extends HuamiFirmwareInfo {
|
|||||||
static {
|
static {
|
||||||
// firmware
|
// firmware
|
||||||
crcToVersion.put(8969, "1.0.5.22");
|
crcToVersion.put(8969, "1.0.5.22");
|
||||||
|
crcToVersion.put(43437, "1.0.5.66");
|
||||||
|
crcToVersion.put(31632, "1.0.6.00");
|
||||||
|
|
||||||
// resources
|
// resources
|
||||||
crcToVersion.put(27412, "1.0.5.22");
|
crcToVersion.put(27412, "1.0.5.22");
|
||||||
|
crcToVersion.put(5466, "1.0.5.66");
|
||||||
|
crcToVersion.put(20047, "1.0.6.00");
|
||||||
|
|
||||||
// font
|
// font
|
||||||
crcToVersion.put(31978, "1");
|
crcToVersion.put(31978, "1");
|
||||||
@ -65,7 +69,7 @@ public class MiBand4FirmwareInfo extends HuamiFirmwareInfo {
|
|||||||
return HuamiFirmwareType.WATCHFACE;
|
return HuamiFirmwareType.WATCHFACE;
|
||||||
}
|
}
|
||||||
if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
|
if (ArrayUtils.startsWith(bytes, NEWFT_HEADER)) {
|
||||||
if (bytes[10] == 0x03) {
|
if (bytes[10] == 0x03 || bytes[10] == 0x06) {
|
||||||
return HuamiFirmwareType.FONT;
|
return HuamiFirmwareType.FONT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,7 @@ public class FetchActivityOperation extends AbstractFetchOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
super.handleActivityFetchFinish(success);
|
super.handleActivityFetchFinish(success);
|
||||||
|
GB.signalActivityDataFinish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean needsAnotherFetch(GregorianCalendar lastSyncTimestamp) {
|
private boolean needsAnotherFetch(GregorianCalendar lastSyncTimestamp) {
|
||||||
|
@ -40,7 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.ActivityDetailsParser;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiActivityDetailsParser;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
@ -86,7 +86,7 @@ public class FetchSportsDetailsOperation extends AbstractFetchOperation {
|
|||||||
|
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
ActivityDetailsParser parser = new ActivityDetailsParser(summary);
|
HuamiActivityDetailsParser parser = new HuamiActivityDetailsParser(summary);
|
||||||
parser.setSkipCounterByte(false); // is already stripped
|
parser.setSkipCounterByte(false); // is already stripped
|
||||||
try {
|
try {
|
||||||
ActivityTrack track = parser.parse(buffer.toByteArray());
|
ActivityTrack track = parser.parse(buffer.toByteArray());
|
||||||
|
@ -43,8 +43,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSportsActivityType;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.BipActivityType;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,6 +154,7 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
|||||||
/**
|
/**
|
||||||
* Buffers the given activity summary data. If the total size is reached,
|
* Buffers the given activity summary data. If the total size is reached,
|
||||||
* it is converted to an object and saved in the database.
|
* it is converted to an object and saved in the database.
|
||||||
|
*
|
||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@ -166,14 +167,14 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
|||||||
ByteBuffer buffer = ByteBuffer.wrap(stream.toByteArray()).order(ByteOrder.LITTLE_ENDIAN);
|
ByteBuffer buffer = ByteBuffer.wrap(stream.toByteArray()).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
// summary.setVersion(BLETypeConversions.toUnsigned(buffer.getShort()));
|
// summary.setVersion(BLETypeConversions.toUnsigned(buffer.getShort()));
|
||||||
short version = buffer.getShort(); // version
|
short version = buffer.getShort(); // version
|
||||||
LOG.debug("Got verison " + version);
|
LOG.debug("Got sport summary version " + version + "total bytes=" + buffer.capacity());
|
||||||
int activityKind = ActivityKind.TYPE_UNKNOWN;
|
int activityKind = ActivityKind.TYPE_UNKNOWN;
|
||||||
try {
|
try {
|
||||||
int rawKind = BLETypeConversions.toUnsigned(buffer.getShort());
|
int rawKind = BLETypeConversions.toUnsigned(buffer.getShort());
|
||||||
BipActivityType activityType = BipActivityType.fromCode(rawKind);
|
HuamiSportsActivityType activityType = HuamiSportsActivityType.fromCode(rawKind);
|
||||||
activityKind = activityType.toActivityKind();
|
activityKind = activityType.toActivityKind();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LOG.error("Error mapping acivity kind: " + ex.getMessage(), ex);
|
LOG.error("Error mapping activity kind: " + ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
summary.setActivityKind(activityKind);
|
summary.setActivityKind(activityKind);
|
||||||
|
|
||||||
@ -197,44 +198,117 @@ public class FetchSportsSummaryOperation extends AbstractFetchOperation {
|
|||||||
summary.setBaseLongitude(baseLongitude);
|
summary.setBaseLongitude(baseLongitude);
|
||||||
summary.setBaseLatitude(baseLatitude);
|
summary.setBaseLatitude(baseLatitude);
|
||||||
summary.setBaseAltitude(baseAltitude);
|
summary.setBaseAltitude(baseAltitude);
|
||||||
|
|
||||||
|
// unused data (for now)
|
||||||
|
float distanceMeters = buffer.getFloat();
|
||||||
|
float ascentMeters = buffer.getFloat();
|
||||||
|
float descentMeters = buffer.getFloat();
|
||||||
|
float maxAltitude = buffer.getFloat();
|
||||||
|
float minAltitude = buffer.getFloat();
|
||||||
|
int maxLatitude = buffer.getInt(); // format?
|
||||||
|
int minLatitude = buffer.getInt(); // format?
|
||||||
|
int maxLongitude = buffer.getInt(); // format?
|
||||||
|
int minLongitude = buffer.getInt(); // format?
|
||||||
|
int steps = buffer.getInt();
|
||||||
|
int activeSeconds = buffer.getInt();
|
||||||
|
float caloriesBurnt = buffer.getFloat();
|
||||||
|
float maxSpeed = buffer.getFloat();
|
||||||
|
float minPace = buffer.getFloat(); // format?
|
||||||
|
float maxPace = buffer.getFloat(); // format?
|
||||||
|
float totalStride = buffer.getFloat();
|
||||||
|
|
||||||
|
buffer.getInt(); // unknown
|
||||||
|
|
||||||
|
if (activityKind == ActivityKind.TYPE_SWIMMING) {
|
||||||
|
// 28 bytes
|
||||||
|
float averageStrokeDistance = buffer.getFloat();
|
||||||
|
float averageStrokesPerSecond = buffer.getFloat();
|
||||||
|
float averageLapPace = buffer.getFloat();
|
||||||
|
short strokes = buffer.getShort();
|
||||||
|
short swolfIndex = buffer.getShort();
|
||||||
|
byte swimStyle = buffer.get();
|
||||||
|
byte laps = buffer.get();
|
||||||
|
buffer.getInt(); // unknown
|
||||||
|
buffer.getInt(); // unknown
|
||||||
|
buffer.getShort(); // unknown
|
||||||
|
|
||||||
|
LOG.debug("unused swim data:" +
|
||||||
|
"\naverageStrokeDistance=" + averageStrokeDistance +
|
||||||
|
"\naverageStrokesPerSecond=" + averageStrokesPerSecond +
|
||||||
|
"\naverageLapPace" + averageLapPace +
|
||||||
|
"\nstrokes=" + strokes +
|
||||||
|
"\nswolfIndex=" + swolfIndex +
|
||||||
|
"\nswimStyle=" + swimStyle + // 1 = breast, 2 = freestyle
|
||||||
|
"\nlaps=" + laps +
|
||||||
|
""
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 28 bytes
|
||||||
|
buffer.getInt(); // unknown
|
||||||
|
buffer.getInt(); // unknown
|
||||||
|
int ascentSeconds = buffer.getInt() / 1000; //ms?
|
||||||
|
buffer.getInt(); // unknown;
|
||||||
|
int descentSeconds = buffer.getInt() / 1000; //ms?
|
||||||
|
buffer.getInt(); // unknown;
|
||||||
|
int flatSeconds = buffer.getInt() / 1000; // ms?
|
||||||
|
LOG.debug("unused non-swim data:" +
|
||||||
|
"\nascentSeconds=" + ascentSeconds +
|
||||||
|
"\ndescentSeconds=" + descentSeconds +
|
||||||
|
"\nflatSeconds=" + flatSeconds +
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
short averageHR = buffer.getShort();
|
||||||
|
short averageKMPaceSeconds = buffer.getShort();
|
||||||
|
short averageStride = buffer.getShort();
|
||||||
|
|
||||||
|
LOG.debug("unused common:" +
|
||||||
|
"\ndistanceMeters=" + distanceMeters +
|
||||||
|
"\nascentMeters=" + ascentMeters +
|
||||||
|
"\ndescentMeters=" + descentMeters +
|
||||||
|
"\nmaxAltitude=" + maxAltitude +
|
||||||
|
"\nminAltitude=" + minAltitude +
|
||||||
|
//"\nmaxLatitude=" + maxLatitude + // not useful
|
||||||
|
//"\nminLatitude=" + minLatitude + // not useful
|
||||||
|
//"\nmaxLongitude=" + maxLongitude + // not useful
|
||||||
|
//"\nminLongitude=" + minLongitude + // not useful
|
||||||
|
"\nsteps=" + steps +
|
||||||
|
"\nactiveSeconds=" + activeSeconds +
|
||||||
|
"\ncaloriesBurnt=" + caloriesBurnt +
|
||||||
|
"\nmaxSpeed=" + maxSpeed +
|
||||||
|
"\nminPace=" + minPace +
|
||||||
|
"\nmaxPace=" + maxPace +
|
||||||
|
"\ntotalStride=" + totalStride +
|
||||||
|
"\naverageHR=" + averageHR +
|
||||||
|
"\naverageKMPaceSeconds=" + averageKMPaceSeconds +
|
||||||
|
"\naverageStride=" + averageStride +
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
// summary.setBaseCoordinate(new GPSCoordinate(baseLatitude, baseLongitude, baseAltitude));
|
// summary.setBaseCoordinate(new GPSCoordinate(baseLatitude, baseLongitude, baseAltitude));
|
||||||
|
// summary.setDistanceMeters(distanceMeters);
|
||||||
// summary.setDistanceMeters(Float.intBitsToFloat(buffer.getInt()));
|
// summary.setAscentMeters(ascentMeters);
|
||||||
// summary.setAscentMeters(Float.intBitsToFloat(buffer.getInt()));
|
// summary.setDescentMeters(descentMeters);
|
||||||
// summary.setDescentMeters(Float.intBitsToFloat(buffer.getInt()));
|
// summary.setMinAltitude(maxAltitude);
|
||||||
//
|
// summary.setMaxAltitude(maxAltitude);
|
||||||
// summary.setMinAltitude(Float.intBitsToFloat(buffer.getInt()));
|
// summary.setMinLatitude(minLatitude);
|
||||||
// summary.setMaxAltitude(Float.intBitsToFloat(buffer.getInt()));
|
// summary.setMaxLatitude(maxLatitude);
|
||||||
// summary.setMinLatitude(buffer.getInt());
|
// summary.setMinLongitude(minLatitude);
|
||||||
// summary.setMaxLatitude(buffer.getInt());
|
// summary.setMaxLongitude(maxLatitude);
|
||||||
// summary.setMinLongitude(buffer.getInt());
|
// summary.setSteps(steps);
|
||||||
// summary.setMaxLongitude(buffer.getInt());
|
// summary.setActiveTimeSeconds(secondsActive);
|
||||||
//
|
// summary.setCaloriesBurnt(caloriesBurnt);
|
||||||
// summary.setSteps(BLETypeConversions.toUnsigned(buffer.getInt()));
|
// summary.setMaxSpeed(maxSpeed);
|
||||||
// summary.setActiveTimeSeconds(BLETypeConversions.toUnsigned(buffer.getInt()));
|
// summary.setMinPace(minPace);
|
||||||
//
|
// summary.setMaxPace(maxPace);
|
||||||
// summary.setCaloriesBurnt(Float.intBitsToFloat(buffer.get()));
|
// summary.setTotalStride(totalStride);
|
||||||
// summary.setMaxSpeed(Float.intBitsToFloat(buffer.get()));
|
// summary.setTimeAscent(BLETypeConversions.toUnsigned(ascentSeconds);
|
||||||
// summary.setMinPace(Float.intBitsToFloat(buffer.get()));
|
// summary.setTimeDescent(BLETypeConversions.toUnsigned(descentSeconds);
|
||||||
// summary.setMaxPace(Float.intBitsToFloat(buffer.get()));
|
// summary.setTimeFlat(BLETypeConversions.toUnsigned(flatSeconds);
|
||||||
// summary.setTotalStride(Float.intBitsToFloat(buffer.get()));
|
// summary.setAverageHR(BLETypeConversions.toUnsigned(averageHR);
|
||||||
|
// summary.setAveragePace(BLETypeConversions.toUnsigned(averagePace);
|
||||||
buffer.getInt(); //
|
// summary.setAverageStride(BLETypeConversions.toUnsigned(averageStride);
|
||||||
buffer.getInt(); //
|
|
||||||
buffer.getInt(); //
|
|
||||||
|
|
||||||
// summary.setTimeAscent(BLETypeConversions.toUnsigned(buffer.getInt()));
|
|
||||||
// buffer.getInt(); //
|
|
||||||
// summary.setTimeDescent(BLETypeConversions.toUnsigned(buffer.getInt()));
|
|
||||||
// buffer.getInt(); //
|
|
||||||
// summary.setTimeFlat(BLETypeConversions.toUnsigned(buffer.getInt()));
|
|
||||||
//
|
|
||||||
// summary.setAverageHR(BLETypeConversions.toUnsigned(buffer.getShort()));
|
|
||||||
//
|
|
||||||
// summary.setAveragePace(BLETypeConversions.toUnsigned(buffer.getShort()));
|
|
||||||
// summary.setAverageStride(BLETypeConversions.toUnsigned(buffer.getShort()));
|
|
||||||
|
|
||||||
buffer.getShort(); //
|
|
||||||
|
|
||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.operations;
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||||
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@ -36,18 +36,17 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipS
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.AbstractFetchOperation;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
public class AmazfitBipFetchLogsOperation extends AbstractFetchOperation {
|
public class HuamiFetchDebugLogsOperation extends AbstractFetchOperation {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipFetchLogsOperation.class);
|
private static final Logger LOG = LoggerFactory.getLogger(HuamiFetchDebugLogsOperation.class);
|
||||||
|
|
||||||
private FileOutputStream logOutputStream;
|
private FileOutputStream logOutputStream;
|
||||||
|
|
||||||
public AmazfitBipFetchLogsOperation(AmazfitBipSupport support) {
|
public HuamiFetchDebugLogsOperation(AmazfitBipSupport support) {
|
||||||
super(support);
|
super(support);
|
||||||
setName("fetch logs");
|
setName("fetch debug logs");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -60,7 +59,7 @@ public class AmazfitBipFetchLogsOperation extends AbstractFetchOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US);
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US);
|
||||||
String filename = "amazfitbip_" + dateFormat.format(new Date()) + ".log";
|
String filename = "huamidebug_" + dateFormat.format(new Date()) + ".log";
|
||||||
|
|
||||||
File outputFile = new File(dir, filename );
|
File outputFile = new File(dir, filename );
|
||||||
try {
|
try {
|
@ -49,9 +49,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
|
||||||
//import java.util.concurrent.Executors;
|
|
||||||
//import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
//import java.util.concurrent.ScheduledFuture;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An operation that fetches activity data. For every fetch, a new operation must
|
* An operation that fetches activity data. For every fetch, a new operation must
|
||||||
@ -71,7 +68,7 @@ public class FetchActivityOperation extends AbstractMiBand1Operation {
|
|||||||
private final boolean hasPacketCounter;
|
private final boolean hasPacketCounter;
|
||||||
|
|
||||||
private class ActivityStruct {
|
private class ActivityStruct {
|
||||||
private int maxDataPacketLength = 20;
|
private int maxDataPacketLength;
|
||||||
private int lastNotifiedProgress;
|
private int lastNotifiedProgress;
|
||||||
private final byte[] activityDataHolder;
|
private final byte[] activityDataHolder;
|
||||||
private final int activityDataHolderSize;
|
private final int activityDataHolderSize;
|
||||||
@ -197,6 +194,7 @@ public class FetchActivityOperation extends AbstractMiBand1Operation {
|
|||||||
activityStruct = null;
|
activityStruct = null;
|
||||||
operationFinished();
|
operationFinished();
|
||||||
unsetBusy();
|
unsetBusy();
|
||||||
|
GB.signalActivityDataFinish();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,293 @@
|
|||||||
|
/* Copyright (C) 2019 Andreas Shimokawa
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.mijia_lywsd02;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.SimpleTimeZone;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
|
||||||
|
|
||||||
|
public class MijiaLywsd02Support extends AbstractBTLEDeviceSupport {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MijiaLywsd02Support.class);
|
||||||
|
private final DeviceInfoProfile<MijiaLywsd02Support> deviceInfoProfile;
|
||||||
|
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
|
||||||
|
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
|
||||||
|
private final IntentListener mListener = new IntentListener() {
|
||||||
|
@Override
|
||||||
|
public void notify(Intent intent) {
|
||||||
|
String s = intent.getAction();
|
||||||
|
if (Objects.equals(s, DeviceInfoProfile.ACTION_DEVICE_INFO)) {
|
||||||
|
handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public MijiaLywsd02Support() {
|
||||||
|
super(LOG);
|
||||||
|
addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
|
||||||
|
addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
|
||||||
|
addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
|
||||||
|
addSupportedService(UUID.fromString("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6"));
|
||||||
|
deviceInfoProfile = new DeviceInfoProfile<>(this);
|
||||||
|
deviceInfoProfile.addListener(mListener);
|
||||||
|
addSupportedProfile(deviceInfoProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||||
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||||
|
requestDeviceInfo(builder);
|
||||||
|
setTime(builder);
|
||||||
|
setInitialized(builder);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTime(TransactionBuilder builder) {
|
||||||
|
BluetoothGattCharacteristic timeCharacteristc = getCharacteristic(UUID.fromString("ebe0ccb7-7a0a-4b0c-8a1a-6ff2997da3a6"));
|
||||||
|
long ts = System.currentTimeMillis();
|
||||||
|
byte offsetHours = (byte) (SimpleTimeZone.getDefault().getOffset(ts) / (1000 * 60 * 60));
|
||||||
|
ts /= 1000;
|
||||||
|
builder.write(timeCharacteristc, new byte[]{
|
||||||
|
(byte) (ts & 0xff),
|
||||||
|
(byte) ((ts >> 8) & 0xff),
|
||||||
|
(byte) ((ts >> 16) & 0xff),
|
||||||
|
(byte) ((ts >> 24) & 0xff),
|
||||||
|
offsetHours});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestDeviceInfo(TransactionBuilder builder) {
|
||||||
|
LOG.debug("Requesting Device Info!");
|
||||||
|
deviceInfoProfile.requestDeviceInfo(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setInitialized(TransactionBuilder builder) {
|
||||||
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useAutoConnect() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
|
||||||
|
LOG.warn("Device info: " + info);
|
||||||
|
versionCmd.hwVersion = info.getHardwareRevision();
|
||||||
|
versionCmd.fwVersion = info.getFirmwareRevision();
|
||||||
|
handleGBDeviceEvent(versionCmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNotification(NotificationSpec notificationSpec) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeleteNotification(int id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetTime() {
|
||||||
|
// better only on connect for now
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetCallState(CallSpec callSpec) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetMusicState(MusicStateSpec stateSpec) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetMusicInfo(MusicSpec musicSpec) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnableRealtimeSteps(boolean enable) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInstallApp(Uri uri) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAppInfoReq() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAppStart(UUID uuid, boolean start) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAppDelete(UUID uuid) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAppConfiguration(UUID appUuid, String config, Integer id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAppReorder(UUID[] uuids) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFetchRecordedData(int dataTypes) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReset(int flags) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeartRateTest() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFindDevice(boolean start) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetConstantVibration(int intensity) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScreenshotReq() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnableHeartRateSleepSupport(boolean enable) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetHeartRateMeasurementInterval(int seconds) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeleteCalendarEvent(byte type, long id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||||
|
BluetoothGattCharacteristic characteristic) {
|
||||||
|
if (super.onCharacteristicChanged(gatt, characteristic)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID characteristicUUID = characteristic.getUuid();
|
||||||
|
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCharacteristicRead(BluetoothGatt gatt,
|
||||||
|
BluetoothGattCharacteristic characteristic, int status) {
|
||||||
|
if (super.onCharacteristicRead(gatt, characteristic, status)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
UUID characteristicUUID = characteristic.getUuid();
|
||||||
|
|
||||||
|
LOG.info("Unhandled characteristic read: " + characteristicUUID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSendConfiguration(String config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReadConfiguration(String config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTestNewFunction() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -628,7 +628,7 @@ public class No1F1Support extends AbstractBTLEDeviceSupport {
|
|||||||
GB.updateTransferNotification(null,"", false, 100, getContext());
|
GB.updateTransferNotification(null,"", false, 100, getContext());
|
||||||
if (getDevice().isBusy()) {
|
if (getDevice().isBusy()) {
|
||||||
getDevice().unsetBusyTask();
|
getDevice().unsetBusyTask();
|
||||||
getDevice().sendDeviceUpdateIntent(getContext());
|
GB.signalActivityDataFinish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
@ -105,7 +105,7 @@ class AppMessageHandlerM7S extends AppMessageHandler {
|
|||||||
return HAIL;
|
return HAIL;
|
||||||
} else if (conditionCode >= 907 && conditionCode < 957) {
|
} else if (conditionCode >= 907 && conditionCode < 957) {
|
||||||
return WIND;
|
return WIND;
|
||||||
} else if (conditionCode == 905 || (conditionCode >= 957 && conditionCode < 900)) {
|
} else if (conditionCode == 905) {
|
||||||
return EXTREME_WIND;
|
return EXTREME_WIND;
|
||||||
} else if (conditionCode == 900) {
|
} else if (conditionCode == 900) {
|
||||||
return TORNADO;
|
return TORNADO;
|
||||||
|
@ -36,6 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleMisfitSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleMisfitSampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMisfitSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMisfitSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
|
||||||
class AppMessageHandlerMisfit extends AppMessageHandler {
|
class AppMessageHandlerMisfit extends AppMessageHandler {
|
||||||
@ -71,6 +72,7 @@ class AppMessageHandlerMisfit extends AppMessageHandler {
|
|||||||
LOG.info("incoming data start");
|
LOG.info("incoming data start");
|
||||||
break;
|
break;
|
||||||
case KEY_INCOMING_DATA_END:
|
case KEY_INCOMING_DATA_END:
|
||||||
|
GB.signalActivityDataFinish();
|
||||||
LOG.info("incoming data end");
|
LOG.info("incoming data end");
|
||||||
break;
|
break;
|
||||||
case KEY_INCOMING_DATA:
|
case KEY_INCOMING_DATA:
|
||||||
|
@ -35,7 +35,6 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSleepMonitorResult;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleMorpheuzSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleMorpheuzSampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMorpheuzSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMorpheuzSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
@ -70,7 +69,7 @@ class AppMessageHandlerMorpheuz extends AppMessageHandler {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AppMessageHandlerMorpheuz.class);
|
private static final Logger LOG = LoggerFactory.getLogger(AppMessageHandlerMorpheuz.class);
|
||||||
|
|
||||||
public AppMessageHandlerMorpheuz(UUID uuid, PebbleProtocol pebbleProtocol) {
|
AppMessageHandlerMorpheuz(UUID uuid, PebbleProtocol pebbleProtocol) {
|
||||||
super(uuid, pebbleProtocol);
|
super(uuid, pebbleProtocol);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -108,16 +107,11 @@ class AppMessageHandlerMorpheuz extends AppMessageHandler {
|
|||||||
@Override
|
@Override
|
||||||
public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
|
public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
|
||||||
int ctrl_message = 0;
|
int ctrl_message = 0;
|
||||||
GBDeviceEventSleepMonitorResult sleepMonitorResult = null;
|
|
||||||
|
|
||||||
for (Pair<Integer, Object> pair : pairs) {
|
for (Pair<Integer, Object> pair : pairs) {
|
||||||
if (Objects.equals(pair.first, keyTransmit)) {
|
if (Objects.equals(pair.first, keyTransmit)) {
|
||||||
sleepMonitorResult = new GBDeviceEventSleepMonitorResult();
|
|
||||||
sleepMonitorResult.smartalarm_from = smartalarm_from;
|
|
||||||
sleepMonitorResult.smartalarm_to = smartalarm_to;
|
|
||||||
sleepMonitorResult.alarm_gone_off = alarm_gone_off;
|
|
||||||
sleepMonitorResult.recording_base_timestamp = recording_base_timestamp;
|
|
||||||
ctrl_message |= CTRL_TRANSMIT_DONE;
|
ctrl_message |= CTRL_TRANSMIT_DONE;
|
||||||
|
GB.signalActivityDataFinish();
|
||||||
} else if (pair.first.equals(keyGoneoff)) {
|
} else if (pair.first.equals(keyGoneoff)) {
|
||||||
alarm_gone_off = (int) pair.second;
|
alarm_gone_off = (int) pair.second;
|
||||||
LOG.info("got gone off: " + alarm_gone_off / 60 + ":" + alarm_gone_off % 60);
|
LOG.info("got gone off: " + alarm_gone_off / 60 + ":" + alarm_gone_off % 60);
|
||||||
@ -187,7 +181,7 @@ class AppMessageHandlerMorpheuz extends AppMessageHandler {
|
|||||||
sendBytesCtrl.encodedBytes = encodeMorpheuzMessage(keyCtrl, ctrl_message);
|
sendBytesCtrl.encodedBytes = encodeMorpheuzMessage(keyCtrl, ctrl_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ctrl and sleep monitor might be null, thats okay
|
// ctrl might be null, thats okay
|
||||||
return new GBDeviceEvent[]{sendBytesAck, sendBytesCtrl, sleepMonitorResult};
|
return new GBDeviceEvent[]{sendBytesAck, sendBytesCtrl};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ class AppMessageHandlerRealWeather extends AppMessageHandler {
|
|||||||
return CLOUD;
|
return CLOUD;
|
||||||
} else if (conditionCode >= 907 && conditionCode < 957) {
|
} else if (conditionCode >= 907 && conditionCode < 957) {
|
||||||
return STORM;
|
return STORM;
|
||||||
} else if (conditionCode == 905 || (conditionCode >= 957 && conditionCode < 900)) {
|
} else if (conditionCode == 905) {
|
||||||
return STORM;
|
return STORM;
|
||||||
} else if (conditionCode == 900) {
|
} else if (conditionCode == 900) {
|
||||||
return STORM;
|
return STORM;
|
||||||
|
@ -88,7 +88,7 @@ private int getConditionForConditionCode(int conditionCode) {
|
|||||||
return HAIL;
|
return HAIL;
|
||||||
} else if (conditionCode >= 907 && conditionCode < 957) {
|
} else if (conditionCode >= 907 && conditionCode < 957) {
|
||||||
return WIND;
|
return WIND;
|
||||||
} else if (conditionCode == 905 || (conditionCode >= 957 && conditionCode < 900)) {
|
} else if (conditionCode == 905) {
|
||||||
return EXTREME_WIND;
|
return EXTREME_WIND;
|
||||||
} else if (conditionCode == 900) {
|
} else if (conditionCode == 900) {
|
||||||
return TORNADO;
|
return TORNADO;
|
||||||
|
@ -130,7 +130,7 @@ class AppMessageHandlerYWeather extends AppMessageHandler {
|
|||||||
return SLEET;
|
return SLEET;
|
||||||
} else if (conditionCode >= 907 && conditionCode < 957) {
|
} else if (conditionCode >= 907 && conditionCode < 957) {
|
||||||
return STORM;
|
return STORM;
|
||||||
} else if (conditionCode == 905 || (conditionCode >= 957 && conditionCode < 900)) {
|
} else if (conditionCode == 905) {
|
||||||
return STORM;
|
return STORM;
|
||||||
} else if (conditionCode == 900) {
|
} else if (conditionCode == 900) {
|
||||||
return STORM;
|
return STORM;
|
||||||
|
@ -65,6 +65,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
public class PebbleProtocol extends GBDeviceProtocol {
|
public class PebbleProtocol extends GBDeviceProtocol {
|
||||||
|
|
||||||
@ -2269,6 +2270,9 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
|||||||
dataLogging.tag = datalogSession.tag;
|
dataLogging.tag = datalogSession.tag;
|
||||||
devEvtsDataLogging = new GBDeviceEvent[]{dataLogging, null};
|
devEvtsDataLogging = new GBDeviceEvent[]{dataLogging, null};
|
||||||
}
|
}
|
||||||
|
if (datalogSession.uuid.equals(UUID_ZERO) && (datalogSession.tag == 81 || datalogSession.tag == 83 || datalogSession.tag == 84)) {
|
||||||
|
GB.signalActivityDataFinish();
|
||||||
|
}
|
||||||
mDatalogSessions.remove(id);
|
mDatalogSessions.remove(id);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -34,6 +34,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
@ -106,6 +107,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
addSupportedService(ZeTimeConstants.UUID_SERVICE_EXTEND);
|
addSupportedService(ZeTimeConstants.UUID_SERVICE_EXTEND);
|
||||||
addSupportedService(ZeTimeConstants.UUID_SERVICE_HEART_RATE);
|
addSupportedService(ZeTimeConstants.UUID_SERVICE_HEART_RATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||||
LOG.info("Initializing");
|
LOG.info("Initializing");
|
||||||
@ -129,6 +131,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
requestBatteryInfo(builder);
|
requestBatteryInfo(builder);
|
||||||
setUserInfo(builder);
|
setUserInfo(builder);
|
||||||
setUserGoals(builder);
|
setUserGoals(builder);
|
||||||
|
setLanguage(builder);
|
||||||
requestActivityInfo(builder);
|
requestActivityInfo(builder);
|
||||||
synchronizeTime(builder);
|
synchronizeTime(builder);
|
||||||
initMusicVolume(builder);
|
initMusicVolume(builder);
|
||||||
@ -143,8 +146,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
public void onSendConfiguration(String config) {
|
public void onSendConfiguration(String config) {
|
||||||
try {
|
try {
|
||||||
TransactionBuilder builder = performInitialized("sendConfiguration");
|
TransactionBuilder builder = performInitialized("sendConfiguration");
|
||||||
switch(config)
|
switch (config) {
|
||||||
{
|
|
||||||
case ZeTimeConstants.PREF_WRIST:
|
case ZeTimeConstants.PREF_WRIST:
|
||||||
setWrist(builder);
|
setWrist(builder);
|
||||||
break;
|
break;
|
||||||
@ -291,8 +293,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
|
|
||||||
for (Alarm alarm : alarms) {
|
for (Alarm alarm : alarms) {
|
||||||
if(remindersOnWatch[alarm.getPosition()][0] == 0)
|
if (remindersOnWatch[alarm.getPosition()][0] == 0) {
|
||||||
{
|
|
||||||
alarmMessage = new byte[]{
|
alarmMessage = new byte[]{
|
||||||
ZeTimeConstants.CMD_PREAMBLE,
|
ZeTimeConstants.CMD_PREAMBLE,
|
||||||
ZeTimeConstants.CMD_REMINDERS,
|
ZeTimeConstants.CMD_REMINDERS,
|
||||||
@ -457,8 +458,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
notification[notification_length - 1] = ZeTimeConstants.CMD_END;
|
notification[notification_length - 1] = ZeTimeConstants.CMD_END;
|
||||||
callIncoming = false;
|
callIncoming = false;
|
||||||
}
|
}
|
||||||
if(notification != null)
|
if (notification != null) {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
TransactionBuilder builder = performInitialized("setCallState");
|
TransactionBuilder builder = performInitialized("setCallState");
|
||||||
sendMsgToWatch(builder, notification);
|
sendMsgToWatch(builder, notification);
|
||||||
@ -628,8 +628,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
if (buildnumber.compareTo("B4.1") >= 0) // if using firmware 1.7 Build 41 and above use newer icons
|
if (buildnumber.compareTo("B4.1") >= 0) // if using firmware 1.7 Build 41 and above use newer icons
|
||||||
{
|
{
|
||||||
weather[9] = Weather.mapToZeTimeCondition(weatherSpec.currentConditionCode);
|
weather[9] = Weather.mapToZeTimeCondition(weatherSpec.currentConditionCode);
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
weather[9] = Weather.mapToZeTimeConditionOld(weatherSpec.currentConditionCode);
|
weather[9] = Weather.mapToZeTimeConditionOld(weatherSpec.currentConditionCode);
|
||||||
}
|
}
|
||||||
for (int forecast = 0; forecast < 3; forecast++) {
|
for (int forecast = 0; forecast < 3; forecast++) {
|
||||||
@ -670,8 +669,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
|
|
||||||
int subject_length = 0;
|
int subject_length = 0;
|
||||||
int body_length = notificationSpec.body.getBytes(StandardCharsets.UTF_8).length;
|
int body_length = notificationSpec.body.getBytes(StandardCharsets.UTF_8).length;
|
||||||
if(body_length > 256)
|
if (body_length > 256) {
|
||||||
{
|
|
||||||
body_length = 256;
|
body_length = 256;
|
||||||
}
|
}
|
||||||
int notification_length = body_length;
|
int notification_length = body_length;
|
||||||
@ -698,26 +696,22 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
(byte) ((time.get(Calendar.SECOND) % 10) + '0'),
|
(byte) ((time.get(Calendar.SECOND) % 10) + '0'),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (notificationSpec.sender != null)
|
if (notificationSpec.sender != null) {
|
||||||
{
|
|
||||||
notification_length += notificationSpec.sender.getBytes(StandardCharsets.UTF_8).length;
|
notification_length += notificationSpec.sender.getBytes(StandardCharsets.UTF_8).length;
|
||||||
subject_length = notificationSpec.sender.getBytes(StandardCharsets.UTF_8).length;
|
subject_length = notificationSpec.sender.getBytes(StandardCharsets.UTF_8).length;
|
||||||
subject = new byte[subject_length];
|
subject = new byte[subject_length];
|
||||||
System.arraycopy(notificationSpec.sender.getBytes(StandardCharsets.UTF_8), 0, subject, 0, subject_length);
|
System.arraycopy(notificationSpec.sender.getBytes(StandardCharsets.UTF_8), 0, subject, 0, subject_length);
|
||||||
} else if(notificationSpec.phoneNumber != null)
|
} else if (notificationSpec.phoneNumber != null) {
|
||||||
{
|
|
||||||
notification_length += notificationSpec.phoneNumber.getBytes(StandardCharsets.UTF_8).length;
|
notification_length += notificationSpec.phoneNumber.getBytes(StandardCharsets.UTF_8).length;
|
||||||
subject_length = notificationSpec.phoneNumber.getBytes(StandardCharsets.UTF_8).length;
|
subject_length = notificationSpec.phoneNumber.getBytes(StandardCharsets.UTF_8).length;
|
||||||
subject = new byte[subject_length];
|
subject = new byte[subject_length];
|
||||||
System.arraycopy(notificationSpec.phoneNumber.getBytes(StandardCharsets.UTF_8), 0, subject, 0, subject_length);
|
System.arraycopy(notificationSpec.phoneNumber.getBytes(StandardCharsets.UTF_8), 0, subject, 0, subject_length);
|
||||||
} else if(notificationSpec.subject != null)
|
} else if (notificationSpec.subject != null) {
|
||||||
{
|
|
||||||
notification_length += notificationSpec.subject.getBytes(StandardCharsets.UTF_8).length;
|
notification_length += notificationSpec.subject.getBytes(StandardCharsets.UTF_8).length;
|
||||||
subject_length = notificationSpec.subject.getBytes(StandardCharsets.UTF_8).length;
|
subject_length = notificationSpec.subject.getBytes(StandardCharsets.UTF_8).length;
|
||||||
subject = new byte[subject_length];
|
subject = new byte[subject_length];
|
||||||
System.arraycopy(notificationSpec.subject.getBytes(StandardCharsets.UTF_8), 0, subject, 0, subject_length);
|
System.arraycopy(notificationSpec.subject.getBytes(StandardCharsets.UTF_8), 0, subject, 0, subject_length);
|
||||||
} else if(notificationSpec.title != null)
|
} else if (notificationSpec.title != null) {
|
||||||
{
|
|
||||||
notification_length += notificationSpec.title.getBytes(StandardCharsets.UTF_8).length;
|
notification_length += notificationSpec.title.getBytes(StandardCharsets.UTF_8).length;
|
||||||
subject_length = notificationSpec.title.getBytes(StandardCharsets.UTF_8).length;
|
subject_length = notificationSpec.title.getBytes(StandardCharsets.UTF_8).length;
|
||||||
subject = new byte[subject_length];
|
subject = new byte[subject_length];
|
||||||
@ -738,8 +732,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
System.arraycopy(datetimeBytes, 0, notification, 9 + subject_length + body_length, datetimeBytes.length);
|
System.arraycopy(datetimeBytes, 0, notification, 9 + subject_length + body_length, datetimeBytes.length);
|
||||||
notification[notification_length - 1] = ZeTimeConstants.CMD_END;
|
notification[notification_length - 1] = ZeTimeConstants.CMD_END;
|
||||||
|
|
||||||
switch(notificationSpec.type)
|
switch (notificationSpec.type) {
|
||||||
{
|
|
||||||
case GENERIC_SMS:
|
case GENERIC_SMS:
|
||||||
notification[5] = ZeTimeConstants.NOTIFICATION_SMS;
|
notification[5] = ZeTimeConstants.NOTIFICATION_SMS;
|
||||||
break;
|
break;
|
||||||
@ -893,28 +886,24 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if (ZeTimeConstants.UUID_NOTIFY_CHARACTERISTIC.equals(characteristicUUID))
|
} else if (ZeTimeConstants.UUID_NOTIFY_CHARACTERISTIC.equals(characteristicUUID)) {
|
||||||
{
|
|
||||||
byte[] data = receiveCompleteMsg(characteristic.getValue());
|
byte[] data = receiveCompleteMsg(characteristic.getValue());
|
||||||
if (isMsgFormatOK(data)) {
|
if (isMsgFormatOK(data)) {
|
||||||
switch (data[1])
|
switch (data[1]) {
|
||||||
{
|
|
||||||
case ZeTimeConstants.CMD_MUSIC_CONTROL:
|
case ZeTimeConstants.CMD_MUSIC_CONTROL:
|
||||||
handleMusicControl(data);
|
handleMusicControl(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
|
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
|
||||||
logMessageContent(characteristic.getValue());
|
logMessageContent(characteristic.getValue());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMsgFormatOK(byte[] msg)
|
private boolean isMsgFormatOK(byte[] msg) {
|
||||||
{
|
|
||||||
if (msg != null) {
|
if (msg != null) {
|
||||||
if (msg[0] == ZeTimeConstants.CMD_PREAMBLE) {
|
if (msg[0] == ZeTimeConstants.CMD_PREAMBLE) {
|
||||||
if ((msg[3] != 0) || (msg[4] != 0)) {
|
if ((msg[3] != 0) || (msg[4] != 0)) {
|
||||||
@ -931,8 +920,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] receiveCompleteMsg(byte[] msg)
|
private byte[] receiveCompleteMsg(byte[] msg) {
|
||||||
{
|
|
||||||
if (msgPart == 0) {
|
if (msgPart == 0) {
|
||||||
int payloadSize = (msg[4] << 8) & 0xff00 | (msg[3] & 0xff);
|
int payloadSize = (msg[4] << 8) & 0xff00 | (msg[3] & 0xff);
|
||||||
if (payloadSize > 14) {
|
if (payloadSize > 14) {
|
||||||
@ -943,8 +931,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
} else {
|
} else {
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
byte[] completeMsg = new byte[lastMsg.length + msg.length];
|
byte[] completeMsg = new byte[lastMsg.length + msg.length];
|
||||||
System.arraycopy(lastMsg, 0, completeMsg, 0, lastMsg.length);
|
System.arraycopy(lastMsg, 0, completeMsg, 0, lastMsg.length);
|
||||||
System.arraycopy(msg, 0, completeMsg, lastMsg.length, msg.length);
|
System.arraycopy(msg, 0, completeMsg, lastMsg.length, msg.length);
|
||||||
@ -1023,11 +1010,9 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
|
|
||||||
private void handleBatteryInfo(byte[] value) {
|
private void handleBatteryInfo(byte[] value) {
|
||||||
batteryCmd.level = ((short) value[5]);
|
batteryCmd.level = ((short) value[5]);
|
||||||
if(batteryCmd.level <= 25)
|
if (batteryCmd.level <= 25) {
|
||||||
{
|
|
||||||
batteryCmd.state = BatteryState.BATTERY_LOW;
|
batteryCmd.state = BatteryState.BATTERY_LOW;
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
batteryCmd.state = BatteryState.BATTERY_NORMAL;
|
batteryCmd.state = BatteryState.BATTERY_NORMAL;
|
||||||
}
|
}
|
||||||
evaluateGBDeviceEvent(batteryCmd);
|
evaluateGBDeviceEvent(batteryCmd);
|
||||||
@ -1036,8 +1021,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
private void handleDeviceInfo(byte[] value) {
|
private void handleDeviceInfo(byte[] value) {
|
||||||
value[value.length - 1] = 0; // convert the end to a String end
|
value[value.length - 1] = 0; // convert the end to a String end
|
||||||
byte[] string = Arrays.copyOfRange(value, 6, value.length - 1);
|
byte[] string = Arrays.copyOfRange(value, 6, value.length - 1);
|
||||||
if(value[5] == 5)
|
if (value[5] == 5) {
|
||||||
{
|
|
||||||
versionCmd.fwVersion = new String(string);
|
versionCmd.fwVersion = new String(string);
|
||||||
} else {
|
} else {
|
||||||
versionCmd.hwVersion = new String(string);
|
versionCmd.hwVersion = new String(string);
|
||||||
@ -1045,24 +1029,20 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
evaluateGBDeviceEvent(versionCmd);
|
evaluateGBDeviceEvent(versionCmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleActivityFetching(byte[] msg)
|
private void handleActivityFetching(byte[] msg) {
|
||||||
{
|
|
||||||
availableStepsData = (int) ((msg[5] & 0xff) | (msg[6] << 8) & 0xff00);
|
availableStepsData = (int) ((msg[5] & 0xff) | (msg[6] << 8) & 0xff00);
|
||||||
availableSleepData = (int) ((msg[7] & 0xff) | (msg[8] << 8) & 0xff00);
|
availableSleepData = (int) ((msg[7] & 0xff) | (msg[8] << 8) & 0xff00);
|
||||||
availableHeartRateData = (int) ((msg[9] & 0xff) | (msg[10] << 8) & 0xff00);
|
availableHeartRateData = (int) ((msg[9] & 0xff) | (msg[10] << 8) & 0xff00);
|
||||||
if (availableStepsData > 0) {
|
if (availableStepsData > 0) {
|
||||||
getStepData();
|
getStepData();
|
||||||
} else if(availableHeartRateData > 0)
|
} else if (availableHeartRateData > 0) {
|
||||||
{
|
|
||||||
getHeartRateData();
|
getHeartRateData();
|
||||||
} else if(availableSleepData > 0)
|
} else if (availableSleepData > 0) {
|
||||||
{
|
|
||||||
getSleepData();
|
getSleepData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getStepData()
|
private void getStepData() {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
TransactionBuilder builder = performInitialized("fetchStepData");
|
TransactionBuilder builder = performInitialized("fetchStepData");
|
||||||
builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
||||||
@ -1080,8 +1060,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteStepData()
|
private void deleteStepData() {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
TransactionBuilder builder = performInitialized("deleteStepData");
|
TransactionBuilder builder = performInitialized("deleteStepData");
|
||||||
sendMsgToWatch(builder, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
sendMsgToWatch(builder, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
||||||
@ -1097,8 +1076,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getHeartRateData()
|
private void getHeartRateData() {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
TransactionBuilder builder = performInitialized("fetchHeartRateData");
|
TransactionBuilder builder = performInitialized("fetchHeartRateData");
|
||||||
builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
||||||
@ -1115,8 +1093,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteHeartRateData()
|
private void deleteHeartRateData() {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
TransactionBuilder builder = performInitialized("deleteHeartRateData");
|
TransactionBuilder builder = performInitialized("deleteHeartRateData");
|
||||||
sendMsgToWatch(builder, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
sendMsgToWatch(builder, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
||||||
@ -1132,8 +1109,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getSleepData()
|
private void getSleepData() {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
TransactionBuilder builder = performInitialized("fetchSleepData");
|
TransactionBuilder builder = performInitialized("fetchSleepData");
|
||||||
builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
builder.write(writeCharacteristic, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
||||||
@ -1151,8 +1127,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteSleepData()
|
private void deleteSleepData() {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
TransactionBuilder builder = performInitialized("deleteSleepData");
|
TransactionBuilder builder = performInitialized("deleteSleepData");
|
||||||
sendMsgToWatch(builder, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
sendMsgToWatch(builder, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
||||||
@ -1168,8 +1143,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleStepsData(byte[] msg)
|
private void handleStepsData(byte[] msg) {
|
||||||
{
|
|
||||||
ZeTimeActivitySample sample = new ZeTimeActivitySample();
|
ZeTimeActivitySample sample = new ZeTimeActivitySample();
|
||||||
Calendar now = GregorianCalendar.getInstance();
|
Calendar now = GregorianCalendar.getInstance();
|
||||||
int timestamp = (msg[10] << 24) & 0xff000000 | (msg[9] << 16) & 0xff0000 | (msg[8] << 8) & 0xff00 | (msg[7] & 0xff);
|
int timestamp = (msg[10] << 24) & 0xff000000 | (msg[9] << 16) & 0xff0000 | (msg[8] << 8) & 0xff00 | (msg[7] & 0xff);
|
||||||
@ -1209,15 +1183,13 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
if (availableHeartRateData > 0) {
|
if (availableHeartRateData > 0) {
|
||||||
getHeartRateData();
|
getHeartRateData();
|
||||||
} else if(availableSleepData > 0)
|
} else if (availableSleepData > 0) {
|
||||||
{
|
|
||||||
getSleepData();
|
getSleepData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSleepData(byte[] msg)
|
private void handleSleepData(byte[] msg) {
|
||||||
{
|
|
||||||
ZeTimeActivitySample sample = new ZeTimeActivitySample();
|
ZeTimeActivitySample sample = new ZeTimeActivitySample();
|
||||||
Calendar now = GregorianCalendar.getInstance();
|
Calendar now = GregorianCalendar.getInstance();
|
||||||
int timestamp = (msg[10] << 24) & 0xff000000 | (msg[9] << 16) & 0xff0000 | (msg[8] << 8) & 0xff00 | (msg[7] & 0xff);
|
int timestamp = (msg[10] << 24) & 0xff000000 | (msg[9] << 16) & 0xff0000 | (msg[8] << 8) & 0xff00 | (msg[7] & 0xff);
|
||||||
@ -1226,11 +1198,9 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sample.setTimestamp(timestamp);
|
sample.setTimestamp(timestamp);
|
||||||
if (msg[11] == 0) {
|
if (msg[11] == 0) {
|
||||||
sample.setRawKind(ActivityKind.TYPE_DEEP_SLEEP);
|
sample.setRawKind(ActivityKind.TYPE_DEEP_SLEEP);
|
||||||
} else if(msg[11] == 1)
|
} else if (msg[11] == 1) {
|
||||||
{
|
|
||||||
sample.setRawKind(ActivityKind.TYPE_LIGHT_SLEEP);
|
sample.setRawKind(ActivityKind.TYPE_LIGHT_SLEEP);
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
sample.setRawKind(ActivityKind.TYPE_UNKNOWN);
|
sample.setRawKind(ActivityKind.TYPE_UNKNOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1253,7 +1223,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
GB.updateTransferNotification(null, "", false, 100, getContext());
|
GB.updateTransferNotification(null, "", false, 100, getContext());
|
||||||
if (getDevice().isBusy()) {
|
if (getDevice().isBusy()) {
|
||||||
getDevice().unsetBusyTask();
|
getDevice().unsetBusyTask();
|
||||||
getDevice().sendDeviceUpdateIntent(getContext());
|
GB.signalActivityDataFinish();
|
||||||
}
|
}
|
||||||
if (!prefs.getBoolean(ZeTimeConstants.PREF_ZETIME_DONT_DEL_ACTDATA, false)) {
|
if (!prefs.getBoolean(ZeTimeConstants.PREF_ZETIME_DONT_DEL_ACTDATA, false)) {
|
||||||
deleteSleepData();
|
deleteSleepData();
|
||||||
@ -1261,8 +1231,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleHeartRateData(byte[] msg)
|
private void handleHeartRateData(byte[] msg) {
|
||||||
{
|
|
||||||
ZeTimeActivitySample sample = new ZeTimeActivitySample();
|
ZeTimeActivitySample sample = new ZeTimeActivitySample();
|
||||||
Calendar now = GregorianCalendar.getInstance();
|
Calendar now = GregorianCalendar.getInstance();
|
||||||
int timestamp = (msg[10] << 24) & 0xff000000 | (msg[9] << 16) & 0xff0000 | (msg[8] << 8) & 0xff00 | (msg[7] & 0xff);
|
int timestamp = (msg[10] << 24) & 0xff000000 | (msg[9] << 16) & 0xff0000 | (msg[8] << 8) & 0xff00 | (msg[7] & 0xff);
|
||||||
@ -1318,23 +1287,19 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
if (!prefs.getBoolean(ZeTimeConstants.PREF_ZETIME_DONT_DEL_ACTDATA, false)) {
|
if (!prefs.getBoolean(ZeTimeConstants.PREF_ZETIME_DONT_DEL_ACTDATA, false)) {
|
||||||
deleteHeartRateData();
|
deleteHeartRateData();
|
||||||
}
|
}
|
||||||
if(availableSleepData > 0)
|
if (availableSleepData > 0) {
|
||||||
{
|
|
||||||
getSleepData();
|
getSleepData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMsgToWatch(TransactionBuilder builder, byte[] msg)
|
private void sendMsgToWatch(TransactionBuilder builder, byte[] msg) {
|
||||||
{
|
if (msg.length > maxMsgLength) {
|
||||||
if(msg.length > maxMsgLength)
|
|
||||||
{
|
|
||||||
int msgpartlength = 0;
|
int msgpartlength = 0;
|
||||||
byte[] msgpart = null;
|
byte[] msgpart = null;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if((msg.length - msgpartlength) < maxMsgLength)
|
if ((msg.length - msgpartlength) < maxMsgLength) {
|
||||||
{
|
|
||||||
msgpart = new byte[msg.length - msgpartlength];
|
msgpart = new byte[msg.length - msgpartlength];
|
||||||
System.arraycopy(msg, msgpartlength, msgpart, 0, msg.length - msgpartlength);
|
System.arraycopy(msg, msgpartlength, msgpart, 0, msg.length - msgpartlength);
|
||||||
msgpartlength += (msg.length - msgpartlength);
|
msgpartlength += (msg.length - msgpartlength);
|
||||||
@ -1345,15 +1310,13 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
builder.write(writeCharacteristic, msgpart);
|
builder.write(writeCharacteristic, msgpart);
|
||||||
} while (msgpartlength < msg.length);
|
} while (msgpartlength < msg.length);
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
builder.write(writeCharacteristic, msg);
|
builder.write(writeCharacteristic, msg);
|
||||||
}
|
}
|
||||||
builder.write(ackCharacteristic, new byte[]{ZeTimeConstants.CMD_ACK_WRITE});
|
builder.write(ackCharacteristic, new byte[]{ZeTimeConstants.CMD_ACK_WRITE});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMusicControl(byte[] musicControlMsg)
|
private void handleMusicControl(byte[] musicControlMsg) {
|
||||||
{
|
|
||||||
if (musicControlMsg[2] == ZeTimeConstants.CMD_SEND) {
|
if (musicControlMsg[2] == ZeTimeConstants.CMD_SEND) {
|
||||||
switch (musicControlMsg[5]) {
|
switch (musicControlMsg[5]) {
|
||||||
case 0: // play current song
|
case 0: // play current song
|
||||||
@ -1411,16 +1374,13 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void replyMsgToWatch(TransactionBuilder builder, byte[] msg)
|
private void replyMsgToWatch(TransactionBuilder builder, byte[] msg) {
|
||||||
{
|
if (msg.length > maxMsgLength) {
|
||||||
if(msg.length > maxMsgLength)
|
|
||||||
{
|
|
||||||
int msgpartlength = 0;
|
int msgpartlength = 0;
|
||||||
byte[] msgpart = null;
|
byte[] msgpart = null;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if((msg.length - msgpartlength) < maxMsgLength)
|
if ((msg.length - msgpartlength) < maxMsgLength) {
|
||||||
{
|
|
||||||
msgpart = new byte[msg.length - msgpartlength];
|
msgpart = new byte[msg.length - msgpartlength];
|
||||||
System.arraycopy(msg, msgpartlength, msgpart, 0, msg.length - msgpartlength);
|
System.arraycopy(msg, msgpartlength, msgpart, 0, msg.length - msgpartlength);
|
||||||
msgpartlength += (msg.length - msgpartlength);
|
msgpartlength += (msg.length - msgpartlength);
|
||||||
@ -1431,14 +1391,12 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
builder.write(replyCharacteristic, msgpart);
|
builder.write(replyCharacteristic, msgpart);
|
||||||
} while (msgpartlength < msg.length);
|
} while (msgpartlength < msg.length);
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
builder.write(replyCharacteristic, msg);
|
builder.write(replyCharacteristic, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void synchronizeTime(TransactionBuilder builder)
|
private void synchronizeTime(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
Calendar now = GregorianCalendar.getInstance();
|
Calendar now = GregorianCalendar.getInstance();
|
||||||
byte[] timeSync = new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
byte[] timeSync = new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
||||||
ZeTimeConstants.CMD_DATE_TIME,
|
ZeTimeConstants.CMD_DATE_TIME,
|
||||||
@ -1462,8 +1420,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// function serving the settings
|
// function serving the settings
|
||||||
private void setWrist(TransactionBuilder builder)
|
private void setWrist(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
String value = GBApplication.getPrefs().getString(ZeTimeConstants.PREF_WRIST, "left");
|
String value = GBApplication.getPrefs().getString(ZeTimeConstants.PREF_WRIST, "left");
|
||||||
|
|
||||||
byte[] wrist = {ZeTimeConstants.CMD_PREAMBLE,
|
byte[] wrist = {ZeTimeConstants.CMD_PREAMBLE,
|
||||||
@ -1481,15 +1438,12 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sendMsgToWatch(builder, wrist);
|
sendMsgToWatch(builder, wrist);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setScreenTime(TransactionBuilder builder)
|
private void setScreenTime(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
int value = GBApplication.getPrefs().getInt(ZeTimeConstants.PREF_SCREENTIME, 30);
|
int value = GBApplication.getPrefs().getInt(ZeTimeConstants.PREF_SCREENTIME, 30);
|
||||||
if(value > ZeTimeConstants.MAX_SCREEN_ON_TIME)
|
if (value > ZeTimeConstants.MAX_SCREEN_ON_TIME) {
|
||||||
{
|
|
||||||
GB.toast(getContext(), "Value for screen on time is greater than 18h! ", Toast.LENGTH_LONG, GB.ERROR);
|
GB.toast(getContext(), "Value for screen on time is greater than 18h! ", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
value = ZeTimeConstants.MAX_SCREEN_ON_TIME;
|
value = ZeTimeConstants.MAX_SCREEN_ON_TIME;
|
||||||
} else if(value < ZeTimeConstants.MIN_SCREEN_ON_TIME)
|
} else if (value < ZeTimeConstants.MIN_SCREEN_ON_TIME) {
|
||||||
{
|
|
||||||
GB.toast(getContext(), "Value for screen on time is lesser than 10s! ", Toast.LENGTH_LONG, GB.ERROR);
|
GB.toast(getContext(), "Value for screen on time is lesser than 10s! ", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
value = ZeTimeConstants.MIN_SCREEN_ON_TIME;
|
value = ZeTimeConstants.MIN_SCREEN_ON_TIME;
|
||||||
}
|
}
|
||||||
@ -1506,8 +1460,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sendMsgToWatch(builder, screentime);
|
sendMsgToWatch(builder, screentime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUserInfo(TransactionBuilder builder)
|
private void setUserInfo(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
ActivityUser activityUser = new ActivityUser();
|
ActivityUser activityUser = new ActivityUser();
|
||||||
byte gender = (byte) activityUser.getGender();
|
byte gender = (byte) activityUser.getGender();
|
||||||
int age = activityUser.getAge();
|
int age = activityUser.getAge();
|
||||||
@ -1517,11 +1470,9 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
if (gender == ActivityUser.GENDER_MALE) // translate gender for zetime
|
if (gender == ActivityUser.GENDER_MALE) // translate gender for zetime
|
||||||
{
|
{
|
||||||
gender = 0;
|
gender = 0;
|
||||||
} else if(gender == ActivityUser.GENDER_FEMALE)
|
} else if (gender == ActivityUser.GENDER_FEMALE) {
|
||||||
{
|
|
||||||
gender = 1;
|
gender = 1;
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
gender = 2;
|
gender = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1539,8 +1490,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sendMsgToWatch(builder, userinfo);
|
sendMsgToWatch(builder, userinfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUserGoals(TransactionBuilder builder)
|
private void setUserGoals(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
ActivityUser activityUser = new ActivityUser();
|
ActivityUser activityUser = new ActivityUser();
|
||||||
int steps = activityUser.getStepsGoal() / 100; // ZeTime expect the steps in 100 increment
|
int steps = activityUser.getStepsGoal() / 100; // ZeTime expect the steps in 100 increment
|
||||||
int calories = activityUser.getCaloriesBurnt();
|
int calories = activityUser.getCaloriesBurnt();
|
||||||
@ -1594,8 +1544,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sendMsgToWatch(builder, goal_activeTime);
|
sendMsgToWatch(builder, goal_activeTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setHeartRateLimits(TransactionBuilder builder)
|
private void setHeartRateLimits(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
|
|
||||||
boolean alarmEnabled = prefs.getBoolean(ZeTimeConstants.PREF_ZETIME_HEARTRATE_ALARM, false);
|
boolean alarmEnabled = prefs.getBoolean(ZeTimeConstants.PREF_ZETIME_HEARTRATE_ALARM, false);
|
||||||
@ -1614,8 +1563,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sendMsgToWatch(builder, heartrateAlarm);
|
sendMsgToWatch(builder, heartrateAlarm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initMusicVolume(TransactionBuilder builder)
|
private void initMusicVolume(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
replyMsgToWatch(builder, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
replyMsgToWatch(builder, new byte[]{ZeTimeConstants.CMD_PREAMBLE,
|
||||||
ZeTimeConstants.CMD_MUSIC_CONTROL,
|
ZeTimeConstants.CMD_MUSIC_CONTROL,
|
||||||
ZeTimeConstants.CMD_REQUEST_RESPOND,
|
ZeTimeConstants.CMD_REQUEST_RESPOND,
|
||||||
@ -1626,8 +1574,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
ZeTimeConstants.CMD_END});
|
ZeTimeConstants.CMD_END});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAnalogMode(TransactionBuilder builder)
|
private void setAnalogMode(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
int mode = prefs.getInt(ZeTimeConstants.PREF_ANALOG_MODE, 0);
|
int mode = prefs.getInt(ZeTimeConstants.PREF_ANALOG_MODE, 0);
|
||||||
|
|
||||||
@ -1642,8 +1589,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sendMsgToWatch(builder, analog);
|
sendMsgToWatch(builder, analog);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setActivityTracking(TransactionBuilder builder)
|
private void setActivityTracking(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
boolean tracking = prefs.getBoolean(ZeTimeConstants.PREF_ACTIVITY_TRACKING, false);
|
boolean tracking = prefs.getBoolean(ZeTimeConstants.PREF_ACTIVITY_TRACKING, false);
|
||||||
|
|
||||||
@ -1654,15 +1600,13 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
(byte) 0x0,
|
(byte) 0x0,
|
||||||
(byte) 0x9,
|
(byte) 0x9,
|
||||||
ZeTimeConstants.CMD_END};
|
ZeTimeConstants.CMD_END};
|
||||||
if(tracking)
|
if (tracking) {
|
||||||
{
|
|
||||||
activity[5] = (byte) 0xa;
|
activity[5] = (byte) 0xa;
|
||||||
}
|
}
|
||||||
sendMsgToWatch(builder, activity);
|
sendMsgToWatch(builder, activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDisplayOnMovement(TransactionBuilder builder)
|
private void setDisplayOnMovement(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
boolean movement = prefs.getBoolean(ZeTimeConstants.PREF_HANDMOVE_DISPLAY, false);
|
boolean movement = prefs.getBoolean(ZeTimeConstants.PREF_HANDMOVE_DISPLAY, false);
|
||||||
|
|
||||||
@ -1675,15 +1619,13 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
(byte) 0xe,
|
(byte) 0xe,
|
||||||
(byte) 0x0,
|
(byte) 0x0,
|
||||||
ZeTimeConstants.CMD_END};
|
ZeTimeConstants.CMD_END};
|
||||||
if(movement)
|
if (movement) {
|
||||||
{
|
|
||||||
handmove[7] = (byte) 0x1;
|
handmove[7] = (byte) 0x1;
|
||||||
}
|
}
|
||||||
sendMsgToWatch(builder, handmove);
|
sendMsgToWatch(builder, handmove);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDoNotDisturb(TransactionBuilder builder)
|
private void setDoNotDisturb(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
String scheduled = prefs.getString(ZeTimeConstants.PREF_DO_NOT_DISTURB, "off");
|
String scheduled = prefs.getString(ZeTimeConstants.PREF_DO_NOT_DISTURB, "off");
|
||||||
String dndScheduled = getContext().getString(R.string.p_scheduled);
|
String dndScheduled = getContext().getString(R.string.p_scheduled);
|
||||||
@ -1711,8 +1653,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
(byte) calendar_end.get(Calendar.MINUTE),
|
(byte) calendar_end.get(Calendar.MINUTE),
|
||||||
ZeTimeConstants.CMD_END};
|
ZeTimeConstants.CMD_END};
|
||||||
|
|
||||||
if(scheduled.equals(dndScheduled))
|
if (scheduled.equals(dndScheduled)) {
|
||||||
{
|
|
||||||
doNotDisturb[5] = (byte) 0x1;
|
doNotDisturb[5] = (byte) 0x1;
|
||||||
}
|
}
|
||||||
sendMsgToWatch(builder, doNotDisturb);
|
sendMsgToWatch(builder, doNotDisturb);
|
||||||
@ -1724,8 +1665,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setCaloriesType(TransactionBuilder builder)
|
private void setCaloriesType(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
int type = prefs.getInt(ZeTimeConstants.PREF_CALORIES_TYPE, 0);
|
int type = prefs.getInt(ZeTimeConstants.PREF_CALORIES_TYPE, 0);
|
||||||
|
|
||||||
@ -1740,8 +1680,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sendMsgToWatch(builder, calories);
|
sendMsgToWatch(builder, calories);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTimeFormate(TransactionBuilder builder)
|
private void setTimeFormate(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
int type = prefs.getInt(ZeTimeConstants.PREF_TIME_FORMAT, 0);
|
int type = prefs.getInt(ZeTimeConstants.PREF_TIME_FORMAT, 0);
|
||||||
|
|
||||||
@ -1763,8 +1702,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sendMsgToWatch(builder, timeformat);
|
sendMsgToWatch(builder, timeformat);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDateFormate(TransactionBuilder builder)
|
private void setDateFormate(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
int type = prefs.getInt(ZeTimeConstants.PREF_DATE_FORMAT, 0);
|
int type = prefs.getInt(ZeTimeConstants.PREF_DATE_FORMAT, 0);
|
||||||
|
|
||||||
@ -1786,14 +1724,12 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sendMsgToWatch(builder, dateformat);
|
sendMsgToWatch(builder, dateformat);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setInactivityAlert(TransactionBuilder builder)
|
private void setInactivityAlert(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
boolean enabled = prefs.getBoolean(ZeTimeConstants.PREF_INACTIVITY_ENABLE, false);
|
boolean enabled = prefs.getBoolean(ZeTimeConstants.PREF_INACTIVITY_ENABLE, false);
|
||||||
int threshold = prefs.getInt(ZeTimeConstants.PREF_INACTIVITY_THRESHOLD, 60);
|
int threshold = prefs.getInt(ZeTimeConstants.PREF_INACTIVITY_THRESHOLD, 60);
|
||||||
|
|
||||||
if(threshold > 0xff)
|
if (threshold > 0xff) {
|
||||||
{
|
|
||||||
threshold = 0xff;
|
threshold = 0xff;
|
||||||
GB.toast(getContext(), "Value for inactivity threshold is greater than 255min! ", Toast.LENGTH_LONG, GB.ERROR);
|
GB.toast(getContext(), "Value for inactivity threshold is greater than 255min! ", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
}
|
}
|
||||||
@ -1815,8 +1751,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
ZeTimeConstants.CMD_END
|
ZeTimeConstants.CMD_END
|
||||||
};
|
};
|
||||||
|
|
||||||
if(enabled)
|
if (enabled) {
|
||||||
{
|
|
||||||
String start = prefs.getString(ZeTimeConstants.PREF_INACTIVITY_START, "06:00");
|
String start = prefs.getString(ZeTimeConstants.PREF_INACTIVITY_START, "06:00");
|
||||||
String end = prefs.getString(ZeTimeConstants.PREF_INACTIVITY_END, "22:00");
|
String end = prefs.getString(ZeTimeConstants.PREF_INACTIVITY_END, "22:00");
|
||||||
DateFormat df_start = new SimpleDateFormat("HH:mm");
|
DateFormat df_start = new SimpleDateFormat("HH:mm");
|
||||||
@ -1855,8 +1790,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sendMsgToWatch(builder, inactivity);
|
sendMsgToWatch(builder, inactivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setShockStrength(TransactionBuilder builder)
|
private void setShockStrength(TransactionBuilder builder) {
|
||||||
{
|
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
int shockStrength = prefs.getInt(ZeTimeConstants.PREF_SHOCK_STRENGTH, 255);
|
int shockStrength = prefs.getInt(ZeTimeConstants.PREF_SHOCK_STRENGTH, 255);
|
||||||
|
|
||||||
@ -1873,8 +1807,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
sendMsgToWatch(builder, strength);
|
sendMsgToWatch(builder, strength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSignaling(TransactionBuilder builder, String signalingType)
|
private void setSignaling(TransactionBuilder builder, String signalingType) {
|
||||||
{
|
|
||||||
Prefs prefs = GBApplication.getPrefs();
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
int signalType = prefs.getInt(signalingType, 0);
|
int signalType = prefs.getInt(signalingType, 0);
|
||||||
|
|
||||||
@ -1889,8 +1822,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
ZeTimeConstants.CMD_END
|
ZeTimeConstants.CMD_END
|
||||||
};
|
};
|
||||||
|
|
||||||
switch(signalingType)
|
switch (signalingType) {
|
||||||
{
|
|
||||||
case ZeTimeConstants.PREF_SMS_SIGNALING:
|
case ZeTimeConstants.PREF_SMS_SIGNALING:
|
||||||
signaling[5] = ZeTimeConstants.SMS_TYPE;
|
signaling[5] = ZeTimeConstants.SMS_TYPE;
|
||||||
break;
|
break;
|
||||||
@ -2025,8 +1957,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getDateTimeFormat(byte[] msg)
|
private void getDateTimeFormat(byte[] msg) {
|
||||||
{
|
|
||||||
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
||||||
|
|
||||||
prefs.putString(ZeTimeConstants.PREF_DATE_FORMAT, Integer.toString(msg[5]));
|
prefs.putString(ZeTimeConstants.PREF_DATE_FORMAT, Integer.toString(msg[5]));
|
||||||
@ -2034,8 +1965,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
prefs.apply();
|
prefs.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getSignaling(byte[] msg)
|
private void getSignaling(byte[] msg) {
|
||||||
{
|
|
||||||
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
||||||
|
|
||||||
prefs.putString(ZeTimeConstants.PREF_ANTI_LOSS_SIGNALING, Integer.toString(msg[5]));
|
prefs.putString(ZeTimeConstants.PREF_ANTI_LOSS_SIGNALING, Integer.toString(msg[5]));
|
||||||
@ -2050,8 +1980,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
prefs.apply();
|
prefs.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getDoNotDisturb(byte[] msg)
|
private void getDoNotDisturb(byte[] msg) {
|
||||||
{
|
|
||||||
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
||||||
String starttime = String.format("%02d:%02d", msg[6], msg[7]);
|
String starttime = String.format("%02d:%02d", msg[6], msg[7]);
|
||||||
String endtime = String.format("%02d:%02d", msg[8], msg[9]);
|
String endtime = String.format("%02d:%02d", msg[8], msg[9]);
|
||||||
@ -2066,38 +1995,32 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
prefs.apply();
|
prefs.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getAnalogMode(byte[] msg)
|
private void getAnalogMode(byte[] msg) {
|
||||||
{
|
|
||||||
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
||||||
|
|
||||||
prefs.putString(ZeTimeConstants.PREF_ANALOG_MODE, Integer.toString(msg[5]));
|
prefs.putString(ZeTimeConstants.PREF_ANALOG_MODE, Integer.toString(msg[5]));
|
||||||
prefs.apply();
|
prefs.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getActivityTracking(byte[] msg)
|
private void getActivityTracking(byte[] msg) {
|
||||||
{
|
|
||||||
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
||||||
|
|
||||||
if(0x1 == msg[6])
|
if (0x1 == msg[6]) {
|
||||||
{
|
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_ACTIVITY_TRACKING, false);
|
prefs.putBoolean(ZeTimeConstants.PREF_ACTIVITY_TRACKING, false);
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_ACTIVITY_TRACKING, true);
|
prefs.putBoolean(ZeTimeConstants.PREF_ACTIVITY_TRACKING, true);
|
||||||
}
|
}
|
||||||
prefs.apply();
|
prefs.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getScreenTime(byte[] msg)
|
private void getScreenTime(byte[] msg) {
|
||||||
{
|
|
||||||
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
||||||
|
|
||||||
prefs.putString(ZeTimeConstants.PREF_SCREENTIME, Integer.toString((msg[5] | (msg[6] << 8))));
|
prefs.putString(ZeTimeConstants.PREF_SCREENTIME, Integer.toString((msg[5] | (msg[6] << 8))));
|
||||||
prefs.apply();
|
prefs.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getWrist(byte[] msg)
|
private void getWrist(byte[] msg) {
|
||||||
{
|
|
||||||
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
||||||
|
|
||||||
if (ZeTimeConstants.WEAR_ON_LEFT_WRIST == msg[5]) {
|
if (ZeTimeConstants.WEAR_ON_LEFT_WRIST == msg[5]) {
|
||||||
@ -2108,79 +2031,67 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
prefs.apply();
|
prefs.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getHeartRateMeasurement(byte[] msg)
|
private void getHeartRateMeasurement(byte[] msg) {
|
||||||
{
|
|
||||||
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
||||||
prefs.putString(ZeTimeConstants.PREF_ZETIME_HEARTRATE_INTERVAL, Integer.toString((msg[5] * 60))); // multiply with 60 because of the conversion from minutes to seconds
|
prefs.putString(ZeTimeConstants.PREF_ZETIME_HEARTRATE_INTERVAL, Integer.toString((msg[5] * 60))); // multiply with 60 because of the conversion from minutes to seconds
|
||||||
prefs.apply();
|
prefs.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getHeartRateLimits(byte[] msg)
|
private void getHeartRateLimits(byte[] msg) {
|
||||||
{
|
|
||||||
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
||||||
prefs.apply();
|
prefs.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getInactivityAlert(byte[] msg)
|
private void getInactivityAlert(byte[] msg) {
|
||||||
{
|
|
||||||
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
||||||
String starttime = String.format("%02d:%02d", msg[7], msg[8]);
|
String starttime = String.format("%02d:%02d", msg[7], msg[8]);
|
||||||
String endtime = String.format("%02d:%02d", msg[9], msg[10]);
|
String endtime = String.format("%02d:%02d", msg[9], msg[10]);
|
||||||
|
|
||||||
prefs.putString(ZeTimeConstants.PREF_INACTIVITY_THRESHOLD, Integer.toString(msg[6]));
|
prefs.putString(ZeTimeConstants.PREF_INACTIVITY_THRESHOLD, Integer.toString(msg[6]));
|
||||||
if(0 != msg[5])
|
if (0 != msg[5]) {
|
||||||
{
|
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_ENABLE, true);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_ENABLE, true);
|
||||||
prefs.putString(ZeTimeConstants.PREF_INACTIVITY_START, starttime);
|
prefs.putString(ZeTimeConstants.PREF_INACTIVITY_START, starttime);
|
||||||
prefs.putString(ZeTimeConstants.PREF_INACTIVITY_END, endtime);
|
prefs.putString(ZeTimeConstants.PREF_INACTIVITY_END, endtime);
|
||||||
if (0 != (msg[5] & (1 << 0))) {
|
if (0 != (msg[5] & (1 << 0))) {
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_MO, true);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_MO, true);
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_MO, false);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_MO, false);
|
||||||
}
|
}
|
||||||
if (0 != (msg[5] & (1 << 1))) {
|
if (0 != (msg[5] & (1 << 1))) {
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_TU, true);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_TU, true);
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_TU, false);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_TU, false);
|
||||||
}
|
}
|
||||||
if (0 != (msg[5] & (1 << 2))) {
|
if (0 != (msg[5] & (1 << 2))) {
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_WE, true);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_WE, true);
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_WE, false);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_WE, false);
|
||||||
}
|
}
|
||||||
if (0 != (msg[5] & (1 << 3))) {
|
if (0 != (msg[5] & (1 << 3))) {
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_TH, true);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_TH, true);
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_TH, false);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_TH, false);
|
||||||
}
|
}
|
||||||
if (0 != (msg[5] & (1 << 4))) {
|
if (0 != (msg[5] & (1 << 4))) {
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_FR, true);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_FR, true);
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_FR, false);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_FR, false);
|
||||||
}
|
}
|
||||||
if (0 != (msg[5] & (1 << 5))) {
|
if (0 != (msg[5] & (1 << 5))) {
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_SA, true);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_SA, true);
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_SA, false);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_SA, false);
|
||||||
}
|
}
|
||||||
if (0 != (msg[5] & (1 << 6))) {
|
if (0 != (msg[5] & (1 << 6))) {
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_SU, true);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_SU, true);
|
||||||
} else
|
} else {
|
||||||
{
|
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_SU, false);
|
prefs.putBoolean(ZeTimeConstants.PREF_INACTIVITY_SU, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prefs.apply();
|
prefs.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getCaloriesType(byte[] msg)
|
private void getCaloriesType(byte[] msg) {
|
||||||
{
|
|
||||||
SharedPreferences prefs = GBApplication.getPrefs().getPreferences();
|
SharedPreferences prefs = GBApplication.getPrefs().getPreferences();
|
||||||
SharedPreferences.Editor myedit = prefs.edit();
|
SharedPreferences.Editor myedit = prefs.edit();
|
||||||
|
|
||||||
@ -2188,8 +2099,7 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
myedit.apply();
|
myedit.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getDisplayOnMovement(byte[] msg)
|
private void getDisplayOnMovement(byte[] msg) {
|
||||||
{
|
|
||||||
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit();
|
||||||
if (0 != (msg[6] & (1 << 6))) {
|
if (0 != (msg[6] & (1 << 6))) {
|
||||||
prefs.putBoolean(ZeTimeConstants.PREF_HANDMOVE_DISPLAY, true);
|
prefs.putBoolean(ZeTimeConstants.PREF_HANDMOVE_DISPLAY, true);
|
||||||
@ -2204,11 +2114,84 @@ public class ZeTimeDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeActualReminders(byte[] msg)
|
private void storeActualReminders(byte[] msg) {
|
||||||
{
|
|
||||||
if (msg[3] == 0xb) // there is a reminder on the watch
|
if (msg[3] == 0xb) // there is a reminder on the watch
|
||||||
{
|
{
|
||||||
System.arraycopy(msg, 6, remindersOnWatch[msg[5] - 1], 0, 10);
|
System.arraycopy(msg, 6, remindersOnWatch[msg[5] - 1], 0, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setLanguage(TransactionBuilder builder) {
|
||||||
|
Prefs prefs = GBApplication.getPrefs();
|
||||||
|
String language = prefs.getString("language", "default");
|
||||||
|
Locale locale;
|
||||||
|
|
||||||
|
byte[] languageMsg = {
|
||||||
|
ZeTimeConstants.CMD_PREAMBLE,
|
||||||
|
ZeTimeConstants.CMD_LANGUAGE_SETTINGS,
|
||||||
|
ZeTimeConstants.CMD_REQUEST,
|
||||||
|
(byte) 0x1,
|
||||||
|
(byte) 0x0,
|
||||||
|
(byte) 0x0,
|
||||||
|
ZeTimeConstants.CMD_END
|
||||||
|
};
|
||||||
|
|
||||||
|
if (language == null || language.equals("default")) {
|
||||||
|
locale = Locale.getDefault();
|
||||||
|
language = locale.getLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (language) {
|
||||||
|
case "zh":
|
||||||
|
languageMsg[5] = 1;
|
||||||
|
break;
|
||||||
|
case "tw":
|
||||||
|
case "hk":
|
||||||
|
case "mo":
|
||||||
|
languageMsg[5] = 2;
|
||||||
|
break;
|
||||||
|
case "ko":
|
||||||
|
languageMsg[5] = 3;
|
||||||
|
break;
|
||||||
|
case "th":
|
||||||
|
languageMsg[5] = 4;
|
||||||
|
break;
|
||||||
|
case "ja":
|
||||||
|
languageMsg[5] = 5;
|
||||||
|
break;
|
||||||
|
case "fr":
|
||||||
|
languageMsg[5] = 6;
|
||||||
|
break;
|
||||||
|
case "es":
|
||||||
|
languageMsg[5] = 7;
|
||||||
|
break;
|
||||||
|
case "de":
|
||||||
|
languageMsg[5] = 8;
|
||||||
|
break;
|
||||||
|
case "it":
|
||||||
|
languageMsg[5] = 9;
|
||||||
|
break;
|
||||||
|
case "pl":
|
||||||
|
languageMsg[5] = 10;
|
||||||
|
break;
|
||||||
|
case "pt":
|
||||||
|
languageMsg[5] = 11;
|
||||||
|
break;
|
||||||
|
case "ru":
|
||||||
|
languageMsg[5] = 12;
|
||||||
|
break;
|
||||||
|
case "nl":
|
||||||
|
languageMsg[5] = 13;
|
||||||
|
break;
|
||||||
|
case "ro":
|
||||||
|
languageMsg[5] = 32;
|
||||||
|
break;
|
||||||
|
case "hu":
|
||||||
|
languageMsg[5] = 33;
|
||||||
|
break;
|
||||||
|
case "en":
|
||||||
|
default:
|
||||||
|
languageMsg[5] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,7 +224,7 @@ public class AndroidUtils {
|
|||||||
Uri contentUri = FileProvider.getUriForFile(context,
|
Uri contentUri = FileProvider.getUriForFile(context,
|
||||||
context.getApplicationContext().getPackageName() + ".screenshot_provider", file);
|
context.getApplicationContext().getPackageName() + ".screenshot_provider", file);
|
||||||
intent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
intent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
intent.setData(contentUri);
|
intent.setDataAndType(contentUri,"application/gpx+xml");
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.jyou.TeclastH30Coordinator;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd02.MijiaLywsd02Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miscale2.MiScale2DeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miscale2.MiScale2DeviceCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
|
||||||
@ -228,6 +229,7 @@ public class DeviceHelper {
|
|||||||
result.add(new Roidmi3Coordinator());
|
result.add(new Roidmi3Coordinator());
|
||||||
result.add(new CasioGB6900DeviceCoordinator());
|
result.add(new CasioGB6900DeviceCoordinator());
|
||||||
result.add(new BFH16DeviceCoordinator());
|
result.add(new BFH16DeviceCoordinator());
|
||||||
|
result.add(new MijiaLywsd02Coordinator());
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,8 @@ import java.nio.ByteOrder;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBEnvironment;
|
import nodomain.freeyourgadget.gadgetbridge.GBEnvironment;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
@ -492,4 +494,9 @@ public class GB {
|
|||||||
throw new AssertionError(errorMessage);
|
throw new AssertionError(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void signalActivityDataFinish() {
|
||||||
|
Intent intent = new Intent(GBApplication.ACTION_NEW_DATA);
|
||||||
|
LocalBroadcastManager.getInstance(GBApplication.getContext()).sendBroadcast(intent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:height="24dp"
|
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="#7E7E7E"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path android:fillColor="#000" android:pathData="M5,20.5A3.5,3.5 0 0,1 1.5,17A3.5,3.5 0 0,1 5,13.5A3.5,3.5 0 0,1 8.5,17A3.5,3.5 0 0,1 5,20.5M5,12A5,5 0 0,0 0,17A5,5 0 0,0 5,22A5,5 0 0,0 10,17A5,5 0 0,0 5,12M14.8,10H19V8.2H15.8L13.86,4.93C13.57,4.43 13,4.1 12.4,4.1C11.93,4.1 11.5,4.29 11.2,4.6L7.5,8.29C7.19,8.6 7,9 7,9.5C7,10.13 7.33,10.66 7.85,10.97L11.2,13V18H13V11.5L10.75,9.85L13.07,7.5M19,20.5A3.5,3.5 0 0,1 15.5,17A3.5,3.5 0 0,1 19,13.5A3.5,3.5 0 0,1 22.5,17A3.5,3.5 0 0,1 19,20.5M19,12A5,5 0 0,0 14,17A5,5 0 0,0 19,22A5,5 0 0,0 24,17A5,5 0 0,0 19,12M16,4.8C17,4.8 17.8,4 17.8,3C17.8,2 17,1.2 16,1.2C15,1.2 14.2,2 14.2,3C14.2,4 15,4.8 16,4.8Z" />
|
<path
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:pathData="M5,20.5A3.5,3.5 0 0,1 1.5,17A3.5,3.5 0 0,1 5,13.5A3.5,3.5 0 0,1 8.5,17A3.5,3.5 0 0,1 5,20.5M5,12A5,5 0 0,0 0,17A5,5 0 0,0 5,22A5,5 0 0,0 10,17A5,5 0 0,0 5,12M14.8,10H19V8.2H15.8L13.86,4.93C13.57,4.43 13,4.1 12.4,4.1C11.93,4.1 11.5,4.29 11.2,4.6L7.5,8.29C7.19,8.6 7,9 7,9.5C7,10.13 7.33,10.66 7.85,10.97L11.2,13V18H13V11.5L10.75,9.85L13.07,7.5M19,20.5A3.5,3.5 0 0,1 15.5,17A3.5,3.5 0 0,1 19,13.5A3.5,3.5 0 0,1 22.5,17A3.5,3.5 0 0,1 19,20.5M19,12A5,5 0 0,0 14,17A5,5 0 0,0 19,22A5,5 0 0,0 24,17A5,5 0 0,0 19,12M16,4.8C17,4.8 17.8,4 17.8,3C17.8,2 17,1.2 16,1.2C15,1.2 14.2,2 14.2,3C14.2,4 15,4.8 16,4.8Z" />
|
||||||
</vector>
|
</vector>
|
@ -1,7 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:height="24dp"
|
|
||||||
android:width="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#000" android:pathData="M23,12H17V10L20.39,6H17V4H23V6L19.62,10H23V12M15,16H9V14L12.39,10H9V8H15V10L11.62,14H15V16M7,20H1V18L4.39,14H1V12H7V14L3.62,18H7V20Z" />
|
|
||||||
</vector>
|
|
10
app/src/main/res/drawable/ic_activity_exercise.xml
Normal file
10
app/src/main/res/drawable/ic_activity_exercise.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="#7E7E7E"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12,2c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2zM21,9h-6v13h-2v-6h-2v6L9,22L9,9L3,9L3,7h18v2z" />
|
||||||
|
</vector>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user