Merge branch 'master' into master
1
.github/ISSUE_TEMPLATE.md
vendored
@ -1,6 +1,7 @@
|
||||
#### 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!*
|
||||
|
60
CHANGELOG.md
@ -1,11 +1,55 @@
|
||||
### Changelog
|
||||
|
||||
###Version 0.19.1
|
||||
#### Version 0.20.2 (next)
|
||||
* Amazfit Bip: Various fixes regarding weather, add condition string support for FW 0.0.8.74
|
||||
* Amazfit Bip: enable caller display in later firmwares
|
||||
* Amazfit Bip: initial firmware update support (EXPERIMENTAL, AT YOUR OWN RISK)
|
||||
|
||||
#### Version 0.20.1
|
||||
* Amazfit Bip: Support icons and text body for notifications
|
||||
* Mi Band: Fix setting smart alarms
|
||||
|
||||
#### Version 0.20.0
|
||||
* Inital Amazfit Bip support (WIP)
|
||||
* Various theming fixes
|
||||
* Add workaround for blacklist not properly persisting
|
||||
* Handle resetting language to default properly
|
||||
* Pebble: Pass booleans from Javascript Appmessage correctly
|
||||
* Pebble: Make local configuration pages work on most recent webview implementation
|
||||
* Pebble: Allow to blacklist calendars
|
||||
* Add Greek and German transliteration support
|
||||
* Various visual improvements to charts
|
||||
|
||||
#### Version 0.19.4
|
||||
* Replace or relicense CC-NC licensed icons to satisfy F-Droid
|
||||
* Mi Band 2: Make infos to display on the Band configurable
|
||||
* Mi Band 2: Support wrist rotation to switch info setting
|
||||
* Mi Band 2: Support goal notification setting
|
||||
* Mi Band 2: Support do not disturb setting
|
||||
* Mi Band 2: Support inactivity warning setting
|
||||
|
||||
#### Version 0.19.3
|
||||
* Pebble: Fix crash when calendar access permission has been denied
|
||||
* Pebble: Fix wrong timestamps with Morpheuz running on Firmware >=3
|
||||
* Mi Band 2: Improve reliability when fetching activity data
|
||||
* HPlus: Fix intensity calculation without continuous connectivity
|
||||
* HPlus: Fix Unicode handling
|
||||
* HPlus: Initial not work detection
|
||||
* Fix memory leak
|
||||
* Only show Realtime Chart on devices supporting it
|
||||
|
||||
#### Version 0.19.2
|
||||
* Pebble: Fix recurring calendar events only appearing once per week
|
||||
* HPlus: Fix crash when receiving calls without phone number
|
||||
* HPlus: Detect unicode support on Zeband Plus
|
||||
* No longer quit Gadgetbridge when bluetooth gets turned off
|
||||
|
||||
#### Version 0.19.1
|
||||
* Fix crash at startup
|
||||
* HPlus: Improve reconnection to device
|
||||
* Improve transliteration
|
||||
|
||||
###Version 0.19.0
|
||||
#### Version 0.19.0
|
||||
* Pebble: allow calendar sync with Timeline (Title, Location, Description)
|
||||
* Pebble: display calendar icon for reminders from AOSP Calendar
|
||||
* HPlus: try to fix latin characters showing as random Chinese text
|
||||
@ -13,7 +57,7 @@
|
||||
* Improve generic notification reliability by trying to restart the notification listener when stale/crashed
|
||||
* Other small bugfixes
|
||||
|
||||
###Version 0.18.5
|
||||
#### Version 0.18.5
|
||||
* Applied some material design guidelines to Charts and (pebble) app management
|
||||
* Changed colours: deep sleep is now dark blue, light sleep is now light blue
|
||||
* Support for exporting and importing of preferences in addition to the database
|
||||
@ -24,24 +68,24 @@
|
||||
* HPlus: users can now decide whether they want to pair the device or not, hopefully fixing some connection problems (#642)
|
||||
* HPlus: display battery state and warn on low battery
|
||||
|
||||
###Version 0.18.4
|
||||
#### Version 0.18.4
|
||||
* Mi Band 2: Display realtime steps in Live Activity
|
||||
* Mi Band: Attempt to recognize Mi Band model with hwVersion = 8
|
||||
* Alarms activity improvements and fixes
|
||||
* Make Buttons in the main activity easier to hit
|
||||
|
||||
###Version 0.18.3
|
||||
#### Version 0.18.3
|
||||
* Fix bug that caused the same value in weekly charts for every day on Android 6 and older
|
||||
|
||||
###Version 0.18.2
|
||||
#### Version 0.18.2
|
||||
* Mi Band 2: Fix crash on "chat" or "social network" text notification (#603)
|
||||
|
||||
###Version 0.18.1
|
||||
#### Version 0.18.1
|
||||
* Pebble: Fix Firmware insstallation on Pebble Time Round (broken since 0.16.0)
|
||||
* Start VibrationActivity when using "find device" button with Vibratissimo
|
||||
* Support material fork of K9
|
||||
|
||||
###Version 0.18.0
|
||||
#### Version 0.18.0
|
||||
* All new GUI for the control center
|
||||
* Add Portuguese pt_PT and pt_BR translations
|
||||
* Add Czech translation
|
||||
|
@ -1,21 +1,27 @@
|
||||
The following artwork is licensed under the following licenses
|
||||
|
||||
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0):
|
||||
Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0):
|
||||
ic_launcher.png
|
||||
ic_device_pebble.png
|
||||
ic_device_miband.png
|
||||
ic_device_lovetoy.png
|
||||
ic_device_hplus.png
|
||||
ic_device_default.png
|
||||
ic_activitytracker.png
|
||||
ic_watchface.png
|
||||
ic_languagepack.png
|
||||
ic_firmware.png
|
||||
ic_watchapp.png
|
||||
ic_systemapp.png
|
||||
icon.png (fastlane metadata directories)
|
||||
featureGraphic.png (fastlane metadata directories)
|
||||
(All of the above by xphnx)
|
||||
|
||||
Creative Commons Attribution-NonCommercial-ShareAlike (CC BY-NC-SA):
|
||||
ic_launcher.png (by Joseph Kim)
|
||||
|
||||
Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0):
|
||||
"GET IT ON F-Droid" button by Laura Kalbag. Source: https://ind.ie/about/blog/f-droid-button/
|
||||
|
||||
Creative Commons Attribution 3.0 Unported license (CC BY-3.0):
|
||||
ic_notification_battery_low.png by Picol.org. Source: https://commons.wikimedia.org/wiki/File:Battery_1_Picol_icon.svg
|
||||
|
||||
Creative Commons Attribution 3.0 United States (CC BY-3.0 US):
|
||||
ic_donate by Peter van Driel https://thenounproject.com/term/donate/239009/
|
||||
|
18
README.md
@ -2,8 +2,13 @@ Gadgetbridge
|
||||
============
|
||||
|
||||
Gadgetbridge is an Android (4.4+) application which will allow you to use your
|
||||
Pebble or Mi Band without the vendor's closed source application and without the
|
||||
need to create an account and transmit any of your data to the vendor's servers.
|
||||
Pebble, Mi Band, Amazfit Bit and HPlus device (and more) without the vendor's closed source application
|
||||
and without the need to create an account and transmit any of your data to the
|
||||
vendor's servers.
|
||||
|
||||
[Homepage](https://gadgetbridge.org)
|
||||
|
||||
[Blog](https://blog.gadgetbridge.org)
|
||||
|
||||
[](https://travis-ci.org/Freeyourgadget/Gadgetbridge)
|
||||
|
||||
@ -11,16 +16,17 @@ need to create an account and transmit any of your data to the vendor's servers.
|
||||
|
||||
[](https://f-droid.org/repository/browse/?fdid=nodomain.freeyourgadget.gadgetbridge)
|
||||
|
||||
[List of changes](CHANGELOG.md)
|
||||
[List of changes](https://github.com/Freeyourgadget/Gadgetbridge/blob/master/CHANGELOG.md)
|
||||
|
||||
## Supported Devices
|
||||
* Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, Pebble Time Round [Wiki section about this device](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble)
|
||||
* Pebble 2 (add the device from within Gadgetbridge!) [Wiki section about pebble](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble), most parts apply to Pebble 2 as well
|
||||
* Mi Band, Mi Band 1A, Mi Band 1S [Wiki section about this device](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band)
|
||||
* Mi Band 2 [Wiki section about mi band](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band), some parts apply to mi band 2 as well
|
||||
* Vibratissimo (experimental)
|
||||
* Liveview
|
||||
* Amazfit Bip (WIP) [Wiki section about the Amazfit Bip](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip)
|
||||
* HPlus Devices (e.g. ZeBand) [Wiki section about this device](https://github.com/Freeyourgadget/Gadgetbridge/wiki/HPlus)
|
||||
* Liveview
|
||||
* Vibratissimo (experimental)
|
||||
|
||||
## Features (Pebble)
|
||||
|
||||
@ -141,7 +147,7 @@ Contributions are welcome, be it feedback, bugreports, documentation, translatio
|
||||
on any of the open [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues?q=is%3Aopen+is%3Aissue);
|
||||
just leave a comment that you're working on one to avoid duplicated work.
|
||||
|
||||
Translations can be contributed via https://www.transifex.com/projects/p/gadgetbridge/resource/strings/ or manually.
|
||||
Translations can be contributed via https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/
|
||||
|
||||
## Do you have further questions or feedback?
|
||||
|
||||
|
@ -26,8 +26,8 @@ android {
|
||||
targetSdkVersion 25
|
||||
|
||||
// note: always bump BOTH versionCode and versionName!
|
||||
versionName "0.19.1"
|
||||
versionCode 94
|
||||
versionName "0.20.2"
|
||||
versionCode 100
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
buildTypes {
|
||||
|
@ -63,6 +63,10 @@
|
||||
android:name=".activities.AppBlacklistActivity"
|
||||
android:label="@string/title_activity_appblacklist"
|
||||
android:parentActivityName=".activities.SettingsActivity" />
|
||||
<activity
|
||||
android:name=".activities.CalBlacklistActivity"
|
||||
android:label="@string/title_activity_calblacklist"
|
||||
android:parentActivityName=".activities.SettingsActivity" />
|
||||
<activity
|
||||
android:name=".activities.FwAppInstallerActivity"
|
||||
android:theme="@style/GadgetbridgeTheme.NoActionBar"
|
||||
@ -110,6 +114,26 @@
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
|
||||
<data android:pathPattern="/.*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\.pbw" />
|
||||
<data android:pathPattern="/.*\\..*\\.pbw" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.pbw" />
|
||||
@ -183,6 +207,26 @@
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.ft" />
|
||||
<data android:pathPattern="/.*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.res" />
|
||||
<data android:pathPattern="/.*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gps" />
|
||||
<data android:pathPattern="/.*\\.pbw" />
|
||||
<data android:pathPattern="/.*\\..*\\.pbw" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.pbw" />
|
||||
|
@ -9,6 +9,18 @@
|
||||
<script type="text/javascript">
|
||||
</script>
|
||||
<style>
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
iframe {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
body {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
|
@ -75,6 +75,13 @@ function showStep(desiredStep) {
|
||||
}
|
||||
}
|
||||
|
||||
function hideSteps() {
|
||||
var steps = document.getElementsByClassName("step");
|
||||
for (var i = 0; i < steps.length; i ++) {
|
||||
steps[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function gbPebble() {
|
||||
this.configurationURL = null;
|
||||
this.configurationValues = null;
|
||||
@ -142,7 +149,18 @@ function gbPebble() {
|
||||
self.configurationURL = new Uri(url).addQueryParam("return_to", "gadgetbridge://"+UUID+"?config=true&json=");
|
||||
} else {
|
||||
//TODO: add custom return_to
|
||||
location.href = url;
|
||||
var iframe = document.getElementsByTagName('iframe')[0];
|
||||
var oldbody = document.getElementsByTagName("body")[0];
|
||||
if (iframe === undefined && oldbody !== undefined) {
|
||||
iframe = document.createElement("iframe");
|
||||
oldbody.parentNode.replaceChild(iframe,oldbody);
|
||||
} else {
|
||||
hideSteps();
|
||||
document.documentElement.appendChild(iframe);
|
||||
}
|
||||
|
||||
iframe.src = url;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -155,11 +173,15 @@ function gbPebble() {
|
||||
try {
|
||||
self.configurationValues = JSON.stringify(dict);
|
||||
document.getElementById("jsondata").innerHTML=self.configurationValues;
|
||||
return callbackAck;
|
||||
if (callbackAck != undefined) {
|
||||
callbackAck();
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
GBjs.gbLog("sendAppMessage failed");
|
||||
return callbackNack;
|
||||
if (callbackNack != undefined) {
|
||||
callbackNack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 28 KiB |
@ -24,6 +24,7 @@ import android.app.NotificationManager.Policy;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
@ -40,6 +41,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
@ -88,6 +90,7 @@ public class GBApplication extends Application {
|
||||
|
||||
public static final String ACTION_QUIT
|
||||
= "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit";
|
||||
public static final String ACTION_LANGUAGE_CHANGE = "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.language_change";
|
||||
|
||||
private static GBApplication app;
|
||||
|
||||
@ -102,6 +105,7 @@ public class GBApplication extends Application {
|
||||
}
|
||||
}
|
||||
};
|
||||
private static Locale language;
|
||||
|
||||
private DeviceManager deviceManager;
|
||||
|
||||
@ -157,9 +161,12 @@ public class GBApplication extends Application {
|
||||
setupExceptionHandler();
|
||||
|
||||
deviceManager = new DeviceManager(this);
|
||||
String language = prefs.getString("language", "default");
|
||||
setLanguage(language);
|
||||
|
||||
deviceService = createDeviceService();
|
||||
loadBlackList();
|
||||
loadAppsBlackList();
|
||||
loadCalendarsBlackList();
|
||||
|
||||
if (isRunningMarshmallowOrLater()) {
|
||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
@ -335,47 +342,107 @@ public class GBApplication extends Application {
|
||||
return NotificationManager.INTERRUPTION_FILTER_ALL;
|
||||
}
|
||||
|
||||
private static HashSet<String> blacklist = null;
|
||||
private static HashSet<String> apps_blacklist = null;
|
||||
|
||||
public static boolean isBlacklisted(String packageName) {
|
||||
return blacklist != null && blacklist.contains(packageName);
|
||||
public static boolean appIsBlacklisted(String packageName) {
|
||||
if (apps_blacklist == null) {
|
||||
GB.log("appIsBlacklisted: apps_blacklist is null!", GB.INFO, null);
|
||||
}
|
||||
return apps_blacklist != null && apps_blacklist.contains(packageName);
|
||||
}
|
||||
|
||||
public static void setBlackList(Set<String> packageNames) {
|
||||
public static void setAppsBlackList(Set<String> packageNames) {
|
||||
if (packageNames == null) {
|
||||
blacklist = new HashSet<>();
|
||||
GB.log("Set null apps_blacklist", GB.INFO, null);
|
||||
apps_blacklist = new HashSet<>();
|
||||
} else {
|
||||
blacklist = new HashSet<>(packageNames);
|
||||
apps_blacklist = new HashSet<>(packageNames);
|
||||
}
|
||||
saveBlackList();
|
||||
GB.log("New apps_blacklist has " + apps_blacklist.size() + " entries", GB.INFO, null);
|
||||
saveAppsBlackList();
|
||||
}
|
||||
|
||||
private static void loadBlackList() {
|
||||
blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.PACKAGE_BLACKLIST, null);
|
||||
if (blacklist == null) {
|
||||
blacklist = new HashSet<>();
|
||||
private static void loadAppsBlackList() {
|
||||
GB.log("Loading apps_blacklist", GB.INFO, null);
|
||||
apps_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.PACKAGE_BLACKLIST, null);
|
||||
if (apps_blacklist == null) {
|
||||
apps_blacklist = new HashSet<>();
|
||||
}
|
||||
GB.log("Loaded apps_blacklist has " + apps_blacklist.size() + " entries", GB.INFO, null);
|
||||
}
|
||||
|
||||
private static void saveBlackList() {
|
||||
private static void saveAppsBlackList() {
|
||||
GB.log("Saving apps_blacklist with " + apps_blacklist.size() + " entries", GB.INFO, null);
|
||||
SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
if (blacklist.isEmpty()) {
|
||||
if (apps_blacklist.isEmpty()) {
|
||||
editor.putStringSet(GBPrefs.PACKAGE_BLACKLIST, null);
|
||||
} else {
|
||||
editor.putStringSet(GBPrefs.PACKAGE_BLACKLIST, blacklist);
|
||||
Prefs.putStringSet(editor, GBPrefs.PACKAGE_BLACKLIST, apps_blacklist);
|
||||
}
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static void addToBlacklist(String packageName) {
|
||||
if (blacklist.add(packageName)) {
|
||||
saveBlackList();
|
||||
public static void addAppToBlacklist(String packageName) {
|
||||
if (apps_blacklist.add(packageName)) {
|
||||
saveAppsBlackList();
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void removeFromBlacklist(String packageName) {
|
||||
blacklist.remove(packageName);
|
||||
saveBlackList();
|
||||
public static synchronized void removeFromAppsBlacklist(String packageName) {
|
||||
GB.log("Removing from apps_blacklist: " + packageName, GB.INFO, null);
|
||||
apps_blacklist.remove(packageName);
|
||||
saveAppsBlackList();
|
||||
}
|
||||
|
||||
private static HashSet<String> calendars_blacklist = null;
|
||||
|
||||
public static boolean calendarIsBlacklisted(String calendarDisplayName) {
|
||||
if (calendars_blacklist == null) {
|
||||
GB.log("calendarIsBlacklisted: calendars_blacklist is null!", GB.INFO, null);
|
||||
}
|
||||
return calendars_blacklist != null && calendars_blacklist.contains(calendarDisplayName);
|
||||
}
|
||||
|
||||
public static void setCalendarsBlackList(Set<String> calendarNames) {
|
||||
if (calendarNames == null) {
|
||||
GB.log("Set null apps_blacklist", GB.INFO, null);
|
||||
calendars_blacklist = new HashSet<>();
|
||||
} else {
|
||||
calendars_blacklist = new HashSet<>(calendarNames);
|
||||
}
|
||||
GB.log("New calendars_blacklist has " + calendars_blacklist.size() + " entries", GB.INFO, null);
|
||||
saveCalendarsBlackList();
|
||||
}
|
||||
|
||||
public static void addCalendarToBlacklist(String calendarDisplayName) {
|
||||
if (calendars_blacklist.add(calendarDisplayName)) {
|
||||
saveCalendarsBlackList();
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeFromCalendarBlacklist(String calendarDisplayName) {
|
||||
calendars_blacklist.remove(calendarDisplayName);
|
||||
saveCalendarsBlackList();
|
||||
}
|
||||
|
||||
private static void loadCalendarsBlackList() {
|
||||
GB.log("Loading calendars_blacklist", GB.INFO, null);
|
||||
calendars_blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.CALENDAR_BLACKLIST, null);
|
||||
if (calendars_blacklist == null) {
|
||||
calendars_blacklist = new HashSet<>();
|
||||
}
|
||||
GB.log("Loaded calendars_blacklist has " + calendars_blacklist.size() + " entries", GB.INFO, null);
|
||||
}
|
||||
|
||||
private static void saveCalendarsBlackList() {
|
||||
GB.log("Saving calendars_blacklist with " + calendars_blacklist.size() + " entries", GB.INFO, null);
|
||||
SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
if (calendars_blacklist.isEmpty()) {
|
||||
editor.putStringSet(GBPrefs.CALENDAR_BLACKLIST, null);
|
||||
} else {
|
||||
Prefs.putStringSet(editor, GBPrefs.CALENDAR_BLACKLIST, calendars_blacklist);
|
||||
}
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -459,6 +526,23 @@ public class GBApplication extends Application {
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static void setLanguage(String lang) {
|
||||
if (lang.equals("default")) {
|
||||
language = Resources.getSystem().getConfiguration().locale;
|
||||
} else {
|
||||
language = new Locale(lang);
|
||||
}
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(language);
|
||||
|
||||
// FIXME: I have no idea what I am doing
|
||||
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(ACTION_LANGUAGE_CHANGE);
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
|
||||
}
|
||||
|
||||
public static LimitedQueue getIDSenderLookup() {
|
||||
return mIDSenderLookup;
|
||||
}
|
||||
@ -496,4 +580,8 @@ public class GBApplication extends Application {
|
||||
public static GBApplication app() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public static Locale getLanguage() {
|
||||
return language;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -17,20 +17,28 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.InputType;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
|
||||
/**
|
||||
* A settings activity with support for preferences directly displaying their value.
|
||||
@ -42,6 +50,22 @@ public abstract class AbstractSettingsActivity extends AppCompatPreferenceActivi
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractSettingsActivity.class);
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case GBApplication.ACTION_LANGUAGE_CHANGE:
|
||||
setLanguage(GBApplication.getLanguage());
|
||||
break;
|
||||
case GBApplication.ACTION_QUIT:
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A preference value change listener that updates the preference's summary
|
||||
* to reflect its new value.
|
||||
@ -110,6 +134,12 @@ public abstract class AbstractSettingsActivity extends AppCompatPreferenceActivi
|
||||
} else {
|
||||
setTheme(R.style.GadgetbridgeTheme);
|
||||
}
|
||||
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(GBApplication.ACTION_LANGUAGE_CHANGE);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@ -127,6 +157,12 @@ public abstract class AbstractSettingsActivity extends AppCompatPreferenceActivi
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses should reimplement this to return the keys of those
|
||||
* preferences which should print its values as a summary below the
|
||||
@ -178,4 +214,8 @@ public abstract class AbstractSettingsActivity extends AppCompatPreferenceActivi
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void setLanguage(Locale language) {
|
||||
AndroidUtils.setLanguage(this, language);
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,8 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.SearchView;
|
||||
@ -32,7 +27,6 @@ import android.view.MenuItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.AppBlacklistAdapter;
|
||||
|
||||
@ -42,16 +36,6 @@ public class AppBlacklistActivity extends GBActivity {
|
||||
|
||||
private AppBlacklistAdapter appBlacklistAdapter;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(GBApplication.ACTION_QUIT)) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -78,10 +62,6 @@ public class AppBlacklistActivity extends GBActivity {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -93,10 +73,4 @@ public class AppBlacklistActivity extends GBActivity {
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,152 @@
|
||||
/* Copyright (C) 2017 Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Paint;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.CalendarContract;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
|
||||
public class CalBlacklistActivity extends GBActivity {
|
||||
|
||||
private final String[] EVENT_PROJECTION = new String[]{
|
||||
CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,
|
||||
CalendarContract.Calendars.CALENDAR_COLOR
|
||||
};
|
||||
private ArrayList<Calendar> calendarsArrayList;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_calblacklist);
|
||||
ListView calListView = (ListView) findViewById(R.id.calListView);
|
||||
|
||||
final Uri uri = CalendarContract.Calendars.CONTENT_URI;
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
|
||||
GB.toast(this, "Calendar permission not granted. Nothing to do.", Toast.LENGTH_SHORT, GB.WARN);
|
||||
return;
|
||||
}
|
||||
try (Cursor cur = getContentResolver().query(uri, EVENT_PROJECTION, null, null, null)) {
|
||||
calendarsArrayList = new ArrayList<>();
|
||||
while (cur != null && cur.moveToNext()) {
|
||||
calendarsArrayList.add(new Calendar(cur.getString(0), cur.getInt(1)));
|
||||
}
|
||||
}
|
||||
|
||||
ArrayAdapter<Calendar> calAdapter = new CalendarListAdapter(this, calendarsArrayList);
|
||||
calListView.setAdapter(calAdapter);
|
||||
calListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int i, long id) {
|
||||
Calendar item = calendarsArrayList.get(i);
|
||||
CheckBox selected = (CheckBox) view.findViewById(R.id.item_checkbox);
|
||||
toggleEntry(view);
|
||||
if (selected.isChecked()) {
|
||||
GBApplication.addCalendarToBlacklist(item.displayName);
|
||||
} else {
|
||||
GBApplication.removeFromCalendarBlacklist(item.displayName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void toggleEntry(View view) {
|
||||
TextView name = (TextView) view.findViewById(R.id.calendar_name);
|
||||
CheckBox checked = (CheckBox) view.findViewById(R.id.item_checkbox);
|
||||
|
||||
name.setPaintFlags(name.getPaintFlags() ^ Paint.STRIKE_THRU_TEXT_FLAG);
|
||||
checked.toggle();
|
||||
}
|
||||
|
||||
class Calendar {
|
||||
private final String displayName;
|
||||
private final int color;
|
||||
|
||||
public Calendar(String displayName, int color) {
|
||||
this.displayName = displayName;
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
private class CalendarListAdapter extends ArrayAdapter<Calendar> {
|
||||
|
||||
CalendarListAdapter(@NonNull Context context, @NonNull List<Calendar> calendars) {
|
||||
super(context, 0, calendars);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) {
|
||||
Calendar item = getItem(position);
|
||||
|
||||
if (view == null) {
|
||||
LayoutInflater inflater = (LayoutInflater) super.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = inflater.inflate(R.layout.item_cal_blacklist, parent, false);
|
||||
}
|
||||
|
||||
View color = view.findViewById(R.id.calendar_color);
|
||||
TextView name = (TextView) view.findViewById(R.id.calendar_name);
|
||||
CheckBox checked = (CheckBox) view.findViewById(R.id.item_checkbox);
|
||||
|
||||
if (GBApplication.calendarIsBlacklisted(item.displayName) && !checked.isChecked()) {
|
||||
toggleEntry(view);
|
||||
}
|
||||
color.setBackgroundColor(item.color);
|
||||
name.setText(item.displayName);
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Canvas;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
@ -49,6 +50,7 @@ import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.cketti.library.changelog.ChangeLog;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -56,6 +58,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAdapterv2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
@ -80,6 +83,9 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case GBApplication.ACTION_LANGUAGE_CHANGE:
|
||||
setLanguage(GBApplication.getLanguage());
|
||||
break;
|
||||
case GBApplication.ACTION_QUIT:
|
||||
finish();
|
||||
break;
|
||||
@ -170,6 +176,7 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
registerForContextMenu(deviceListView);
|
||||
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(GBApplication.ACTION_LANGUAGE_CHANGE);
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(DeviceManager.ACTION_DEVICES_CHANGED);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
@ -189,7 +196,7 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
checkAndRequestPermissions();
|
||||
}
|
||||
|
||||
ChangeLog cl = new ChangeLog(this);
|
||||
ChangeLog cl = createChangeLog();
|
||||
if (cl.isFirstRun()) {
|
||||
cl.getLogDialog().show();
|
||||
}
|
||||
@ -203,6 +210,13 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
unregisterForContextMenu(deviceListView);
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||
@ -235,8 +249,13 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
case R.id.action_quit:
|
||||
GBApplication.quit();
|
||||
return true;
|
||||
case R.id.donation_link:
|
||||
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("https://liberapay.com/Gadgetbridge")); //TODO: centralize if ever used somewhere else
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(i);
|
||||
return true;
|
||||
case R.id.external_changelog:
|
||||
ChangeLog cl = new ChangeLog(this);
|
||||
ChangeLog cl = createChangeLog();
|
||||
cl.getFullLogDialog().show();
|
||||
return true;
|
||||
}
|
||||
@ -244,6 +263,14 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
return true;
|
||||
}
|
||||
|
||||
private ChangeLog createChangeLog() {
|
||||
String css = ChangeLog.DEFAULT_CSS;
|
||||
css += "body { "
|
||||
+ "color: " + AndroidUtils.getTextColorHex(getBaseContext()) + "; "
|
||||
+ "background-color: " + AndroidUtils.getBackgroundColorHex(getBaseContext()) + ";" +
|
||||
"}";
|
||||
return new ChangeLog(this, css);
|
||||
}
|
||||
private void launchDiscoveryActivity() {
|
||||
startActivity(new Intent(this, DiscoveryActivity.class));
|
||||
}
|
||||
@ -288,4 +315,7 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[wantedPermissions.size()]), 0);
|
||||
}
|
||||
|
||||
private void setLanguage(Locale language) {
|
||||
AndroidUtils.setLanguage(this, language);
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Bundle;
|
||||
@ -62,9 +61,6 @@ public class DbManagementActivity extends GBActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_db_management);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
|
||||
dbPath = (TextView) findViewById(R.id.activity_db_management_path);
|
||||
dbPath.setText(getExternalPath());
|
||||
|
||||
|
@ -78,10 +78,6 @@ public class DebugActivity extends GBActivity {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case GBApplication.ACTION_QUIT: {
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
case ACTION_REPLY: {
|
||||
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||
CharSequence reply = remoteInput.getCharSequence(EXTRA_REPLY);
|
||||
@ -104,7 +100,6 @@ public class DebugActivity extends GBActivity {
|
||||
setContentView(R.layout.activity_debug);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
filter.addAction(ACTION_REPLY);
|
||||
filter.addAction(DeviceService.ACTION_HEARTRATE_MEASUREMENT);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
|
@ -221,9 +221,6 @@ public class ExternalPebbleJSActivity extends GBActivity {
|
||||
|
||||
if (passKey) {
|
||||
Object obj = in.get(inKey);
|
||||
if (obj instanceof Boolean) {
|
||||
obj = ((Boolean) obj) ? "true" : "false";
|
||||
}
|
||||
out.put(outKey, obj);
|
||||
} else {
|
||||
GB.toast("Discarded key " + inKey + ", not found in the local configuration and is not an integer key.", Toast.LENGTH_SHORT, GB.WARN);
|
||||
|
@ -97,9 +97,7 @@ public class FwAppInstallerActivity extends AppCompatActivity implements Install
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (GBApplication.ACTION_QUIT.equals(action)) {
|
||||
finish();
|
||||
} else if (GBDevice.ACTION_DEVICE_CHANGED.equals(action)) {
|
||||
if (GBDevice.ACTION_DEVICE_CHANGED.equals(action)) {
|
||||
device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
if (device != null) {
|
||||
refreshBusyState(device);
|
||||
@ -181,7 +179,6 @@ public class FwAppInstallerActivity extends AppCompatActivity implements Install
|
||||
detailsListView.setAdapter(mDetailsItemAdapter);
|
||||
setInstallEnabled(false);
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
filter.addAction(GB.ACTION_DISPLAY_MESSAGE);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
|
@ -17,44 +17,64 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.os.LocaleList;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
|
||||
public class GBActivity extends AppCompatActivity {
|
||||
private void setLanguage(String language) {
|
||||
Locale locale;
|
||||
if (language.equals("default")) {
|
||||
locale = Locale.getDefault();
|
||||
} else {
|
||||
locale = new Locale(language);
|
||||
}
|
||||
Configuration config = new Configuration();
|
||||
config.locale = locale;
|
||||
|
||||
// FIXME: I have no idea what I am doing
|
||||
getApplicationContext().getResources().updateConfiguration(config, getApplicationContext().getResources().getDisplayMetrics());
|
||||
getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case GBApplication.ACTION_LANGUAGE_CHANGE:
|
||||
setLanguage(GBApplication.getLanguage());
|
||||
break;
|
||||
case GBApplication.ACTION_QUIT:
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void setLanguage(Locale language) {
|
||||
AndroidUtils.setLanguage(this, language);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(GBApplication.ACTION_LANGUAGE_CHANGE);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
if (GBApplication.isDarkThemeEnabled()) {
|
||||
setTheme(R.style.GadgetbridgeThemeDark);
|
||||
} else {
|
||||
setTheme(R.style.GadgetbridgeTheme);
|
||||
}
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
String language = prefs.getString("language", "default");
|
||||
setLanguage(language);
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
@ -97,6 +97,15 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
}
|
||||
});
|
||||
|
||||
pref = findPreference("pref_key_blacklist_calendars");
|
||||
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent enableIntent = new Intent(SettingsActivity.this, CalBlacklistActivity.class);
|
||||
startActivity(enableIntent);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
pref = findPreference("pebble_emu_addr");
|
||||
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
@ -143,6 +152,25 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
|
||||
});
|
||||
|
||||
pref = findPreference("language");
|
||||
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
String newLang = newVal.toString();
|
||||
try {
|
||||
GBApplication.setLanguage(newLang);
|
||||
} catch (Exception ex) {
|
||||
GB.toast(getApplicationContext(),
|
||||
"Error setting language: " + ex.getLocalizedMessage(),
|
||||
Toast.LENGTH_LONG,
|
||||
GB.ERROR,
|
||||
ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (!GBApplication.isRunningMarshmallowOrLater()) {
|
||||
pref = findPreference("notification_filter");
|
||||
PreferenceCategory category = (PreferenceCategory) findPreference("pref_key_notifications");
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -16,12 +16,7 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -33,17 +28,6 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class VibrationActivity extends GBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VibrationActivity.class);
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case GBApplication.ACTION_QUIT: {
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
private SeekBar seekBar;
|
||||
|
||||
@Override
|
||||
@ -51,11 +35,6 @@ public class VibrationActivity extends GBActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_vibration);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
registerReceiver(mReceiver, filter);
|
||||
|
||||
seekBar = (SeekBar) findViewById(R.id.vibration_seekbar);
|
||||
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
@ -77,11 +56,4 @@ public class VibrationActivity extends GBActivity {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
unregisterReceiver(mReceiver);
|
||||
}
|
||||
}
|
||||
|
@ -18,17 +18,13 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@ -45,7 +41,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractFragmentPagerAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragmentActivity;
|
||||
@ -61,18 +56,6 @@ public class AppManagerActivity extends AbstractGBFragmentActivity {
|
||||
|
||||
private GBDevice mGBDevice = null;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case GBApplication.ACTION_QUIT:
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public GBDevice getGBDevice() {
|
||||
return mGBDevice;
|
||||
}
|
||||
@ -104,11 +87,6 @@ public class AppManagerActivity extends AbstractGBFragmentActivity {
|
||||
}
|
||||
});
|
||||
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
// Set up the ViewPager with the sections adapter.
|
||||
ViewPager viewPager = (ViewPager) findViewById(R.id.appmanager_pager);
|
||||
if (viewPager != null) {
|
||||
@ -232,10 +210,4 @@ public class AppManagerActivity extends AbstractGBFragmentActivity {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
@ -487,7 +487,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
}
|
||||
activityEntries.add(createBarEntry(value, ts));
|
||||
if (hr && isValidHeartRateValue(sample.getHeartRate())) {
|
||||
if (lastHrSampleIndex > -1 && ts - lastHrSampleIndex > 60*HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
|
||||
if (lastHrSampleIndex > -1 && ts - lastHrSampleIndex > 1800*HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
|
||||
heartrateEntries.add(createLineEntry(0, lastHrSampleIndex + 1));
|
||||
heartrateEntries.add(createLineEntry(0, ts - 1));
|
||||
}
|
||||
@ -530,7 +530,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
List<IBarDataSet> list = new ArrayList<>();
|
||||
list.add(activitySet);
|
||||
BarData barData = new BarData(list);
|
||||
barData.setBarWidth(100f);
|
||||
barData.setBarWidth(200f);
|
||||
// barData.setGroupSpace(0);
|
||||
combinedData.setData(barData);
|
||||
|
||||
@ -595,10 +595,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
|
||||
protected LineDataSet createHeartrateSet(List<Entry> values, String label) {
|
||||
LineDataSet set1 = new LineDataSet(values, label);
|
||||
set1.setLineWidth(0.8f);
|
||||
set1.setLineWidth(2.2f);
|
||||
set1.setColor(HEARTRATE_COLOR);
|
||||
// set1.setDrawCubic(true);
|
||||
set1.setMode(LineDataSet.Mode.CUBIC_BEZIER);
|
||||
set1.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
|
||||
set1.setCubicIntensity(0.1f);
|
||||
set1.setDrawCircles(false);
|
||||
// set1.setCircleRadius(2f);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Gobbetti, Vebryn
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -17,7 +17,12 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
|
||||
@ -25,6 +30,13 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
class ActivityAnalysis {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ActivityAnalysis.class);
|
||||
|
||||
// store raw steps and duration
|
||||
protected HashMap<Integer, Long> stats = new HashMap<Integer, Long>();
|
||||
// max speed determined from samples
|
||||
private int maxSpeed = 0;
|
||||
|
||||
ActivityAmounts calculateActivityAmounts(List<? extends ActivitySample> samples) {
|
||||
ActivityAmount deepSleep = new ActivityAmount(ActivityKind.TYPE_DEEP_SLEEP);
|
||||
ActivityAmount lightSleep = new ActivityAmount(ActivityKind.TYPE_LIGHT_SLEEP);
|
||||
@ -53,7 +65,7 @@ class ActivityAnalysis {
|
||||
|
||||
int steps = sample.getSteps();
|
||||
if (steps > 0) {
|
||||
amount.addSteps(sample.getSteps());
|
||||
amount.addSteps(steps);
|
||||
}
|
||||
|
||||
if (previousSample != null) {
|
||||
@ -65,6 +77,22 @@ class ActivityAnalysis {
|
||||
previousAmount.addSeconds(sharedTimeDifference);
|
||||
amount.addSeconds(sharedTimeDifference);
|
||||
}
|
||||
|
||||
// add time
|
||||
if (steps > 0 && sample.getKind() == ActivityKind.TYPE_ACTIVITY) {
|
||||
if (steps > maxSpeed) {
|
||||
maxSpeed = steps;
|
||||
}
|
||||
|
||||
if (!stats.containsKey(steps)) {
|
||||
LOG.info("Adding: " + steps);
|
||||
stats.put(steps, timeDifference);
|
||||
} else {
|
||||
long time = stats.get(steps);
|
||||
LOG.info("Updating: " + steps + " " + timeDifference + time);
|
||||
stats.put(steps, timeDifference + time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousAmount = amount;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Gobbetti, Vebryn
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -102,9 +102,6 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case GBApplication.ACTION_QUIT:
|
||||
finish();
|
||||
break;
|
||||
case GBDevice.ACTION_DEVICE_CHANGED:
|
||||
GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
refreshBusyState(dev);
|
||||
@ -136,7 +133,6 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
initDates();
|
||||
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
@ -333,6 +329,8 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
return new WeekStepsChartFragment();
|
||||
case 4:
|
||||
return new LiveActivityFragment();
|
||||
case 5:
|
||||
return new SpeedZonesFragment();
|
||||
|
||||
}
|
||||
return null;
|
||||
@ -340,7 +338,11 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
// Show 5 total pages.
|
||||
// Show 5 or 6 total pages.
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice);
|
||||
if (coordinator.supportsRealtimeData()) {
|
||||
return 6;
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
|
||||
@ -357,6 +359,8 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
return getString(R.string.weekstepschart_steps_a_week);
|
||||
case 4:
|
||||
return getString(R.string.liveactivity_live_activity);
|
||||
case 5:
|
||||
return getString(R.string.stats_title);
|
||||
}
|
||||
return super.getPageTitle(position);
|
||||
}
|
||||
|
@ -0,0 +1,170 @@
|
||||
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Vebryn
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.github.mikephil.charting.charts.Chart;
|
||||
import com.github.mikephil.charting.charts.HorizontalBarChart;
|
||||
import com.github.mikephil.charting.components.XAxis;
|
||||
import com.github.mikephil.charting.components.YAxis;
|
||||
import com.github.mikephil.charting.data.BarData;
|
||||
import com.github.mikephil.charting.data.BarDataSet;
|
||||
import com.github.mikephil.charting.data.BarEntry;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
|
||||
|
||||
public class SpeedZonesFragment extends AbstractChartFragment {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(SpeedZonesFragment.class);
|
||||
|
||||
private HorizontalBarChart mStatsChart;
|
||||
|
||||
@Override
|
||||
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
|
||||
List<? extends ActivitySample> samples = getSamples(db, device);
|
||||
|
||||
MySpeedZonesData mySpeedZonesData = refreshStats(samples);
|
||||
|
||||
return new MyChartsData(mySpeedZonesData);
|
||||
}
|
||||
|
||||
private MySpeedZonesData refreshStats(List<? extends ActivitySample> samples) {
|
||||
ActivityAnalysis analysis = new ActivityAnalysis();
|
||||
analysis.calculateActivityAmounts(samples);
|
||||
BarData data = new BarData();
|
||||
data.setValueTextColor(CHART_TEXT_COLOR);
|
||||
List<BarEntry> entries = new ArrayList<>();
|
||||
|
||||
ActivityUser user = new ActivityUser();
|
||||
/*double distanceFactorCm;
|
||||
if (user.getGender() == user.GENDER_MALE){
|
||||
distanceFactorCm = user.getHeightCm() * user.GENDER_MALE_DISTANCE_FACTOR / 1000;
|
||||
} else {
|
||||
distanceFactorCm = user.getHeightCm() * user.GENDER_FEMALE_DISTANCE_FACTOR / 1000;
|
||||
}*/
|
||||
|
||||
for (Map.Entry<Integer, Long> entry : analysis.stats.entrySet()) {
|
||||
entries.add(new BarEntry(entry.getKey(), entry.getValue() / 60));
|
||||
}
|
||||
|
||||
BarDataSet set = new BarDataSet(entries, "");
|
||||
set.setValueTextColor(CHART_TEXT_COLOR);
|
||||
set.setColors(getColorFor(ActivityKind.TYPE_ACTIVITY));
|
||||
//set.setDrawValues(false);
|
||||
//data.setBarWidth(0.1f);
|
||||
data.addDataSet(set);
|
||||
|
||||
return new MySpeedZonesData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateChartsnUIThread(ChartsData chartsData) {
|
||||
MyChartsData mcd = (MyChartsData) chartsData;
|
||||
mStatsChart.setData(mcd.getChartsData().getBarData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return getString(R.string.stats_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_statschart, container, false);
|
||||
|
||||
mStatsChart = (HorizontalBarChart) rootView.findViewById(R.id.statschart);
|
||||
setupStatsChart();
|
||||
|
||||
// refresh immediately instead of use refreshIfVisible(), for perceived performance
|
||||
refresh();
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void setupStatsChart() {
|
||||
mStatsChart.setBackgroundColor(BACKGROUND_COLOR);
|
||||
mStatsChart.getDescription().setTextColor(DESCRIPTION_COLOR);
|
||||
mStatsChart.setNoDataText("");
|
||||
mStatsChart.getLegend().setEnabled(false);
|
||||
mStatsChart.setTouchEnabled(false);
|
||||
mStatsChart.getDescription().setText("");
|
||||
|
||||
XAxis x = mStatsChart.getXAxis();
|
||||
x.setTextColor(CHART_TEXT_COLOR);
|
||||
|
||||
YAxis yr = mStatsChart.getAxisRight();
|
||||
yr.setTextColor(CHART_TEXT_COLOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
return super.getAllSamples(db, device, tsFrom, tsTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupLegend(Chart chart) {
|
||||
// no legend here, it is all about the steps here
|
||||
chart.getLegend().setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderCharts() {
|
||||
mStatsChart.invalidate();
|
||||
}
|
||||
|
||||
private static class MySpeedZonesData extends ChartsData {
|
||||
private final BarData barData;
|
||||
|
||||
MySpeedZonesData(BarData barData) {
|
||||
this.barData = barData;
|
||||
}
|
||||
|
||||
BarData getBarData() {
|
||||
return barData;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyChartsData extends ChartsData {
|
||||
private final MySpeedZonesData chartsData;
|
||||
|
||||
MyChartsData(MySpeedZonesData chartsData) {
|
||||
this.chartsData = chartsData;
|
||||
}
|
||||
|
||||
MySpeedZonesData getChartsData() {
|
||||
return chartsData;
|
||||
}
|
||||
}
|
||||
}
|
@ -62,7 +62,7 @@ public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapte
|
||||
if (name == null) {
|
||||
name = ai.packageName;
|
||||
}
|
||||
if (GBApplication.isBlacklisted(ai.packageName)) {
|
||||
if (GBApplication.appIsBlacklisted(ai.packageName)) {
|
||||
// sort blacklisted first by prefixing with a '!'
|
||||
name = "!" + name;
|
||||
}
|
||||
@ -94,7 +94,7 @@ public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapte
|
||||
holder.deviceAppNameLabel.setText(mNameMap.get(appInfo));
|
||||
holder.deviceImageView.setImageDrawable(appInfo.loadIcon(mPm));
|
||||
|
||||
holder.checkbox.setChecked(GBApplication.isBlacklisted(appInfo.packageName));
|
||||
holder.checkbox.setChecked(GBApplication.appIsBlacklisted(appInfo.packageName));
|
||||
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -102,9 +102,9 @@ public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapte
|
||||
CheckBox checkBox = ((CheckBox) v.findViewById(R.id.item_checkbox));
|
||||
checkBox.toggle();
|
||||
if (checkBox.isChecked()) {
|
||||
GBApplication.addToBlacklist(appInfo.packageName);
|
||||
GBApplication.addAppToBlacklist(appInfo.packageName);
|
||||
} else {
|
||||
GBApplication.removeFromBlacklist(appInfo.packageName);
|
||||
GBApplication.removeFromAppsBlacklist(appInfo.packageName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -189,6 +189,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
|
||||
public void onClick(View v) {
|
||||
Intent startIntent;
|
||||
startIntent = new Intent(context, ConfigureAlarms.class);
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
context.startActivity(startIntent);
|
||||
}
|
||||
}
|
||||
|
@ -201,8 +201,6 @@ public interface DeviceCoordinator {
|
||||
*/
|
||||
boolean supportsHeartRateMeasurement(GBDevice device);
|
||||
|
||||
int getTapString();
|
||||
|
||||
/**
|
||||
* Returns the readable name of the manufacturer.
|
||||
*/
|
||||
@ -234,4 +232,10 @@ public interface DeviceCoordinator {
|
||||
*/
|
||||
boolean supportsCalendarEvents();
|
||||
|
||||
/**
|
||||
* Indicates whether the device supports getting a stream of live data.
|
||||
* This can be live HR, steps etc.
|
||||
*/
|
||||
boolean supportsRealtimeData();
|
||||
|
||||
}
|
||||
|
@ -157,11 +157,6 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "unknown";
|
||||
@ -181,4 +176,9 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,60 @@
|
||||
/* Copyright (C) 2017 Andreas Shimokawa, João Paulo Barraca
|
||||
|
||||
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.amazfitbip;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class AmazfitBipCooordinator extends MiBand2Coordinator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipCooordinator.class);
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.AMAZFITBIP;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
try {
|
||||
BluetoothDevice device = candidate.getDevice();
|
||||
String name = device.getName();
|
||||
if (name != null && name.equalsIgnoreCase("Amazfit Bip Watch")) {
|
||||
return DeviceType.AMAZFITBIP;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.error("unable to check device support", ex);
|
||||
}
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
AmazfitBipFWInstallHandler handler = new AmazfitBipFWInstallHandler(uri, context);
|
||||
return handler.isValid() ? handler : null;
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/* Copyright (C) 2016-2017 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.devices.amazfitbip;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.AmazfitBipFirmwareInfo;
|
||||
|
||||
public class AmazfitBipFWHelper extends AbstractMiBandFWHelper {
|
||||
|
||||
public AmazfitBipFWHelper(Uri uri, Context context) throws IOException {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
private AmazfitBipFirmwareInfo firmwareInfo;
|
||||
|
||||
@Override
|
||||
public String format(int version) {
|
||||
return AmazfitBipFirmwareInfo.toVersion(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirmwareVersion() {
|
||||
return firmwareInfo.getFirmwareVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFirmware2Version() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHumanFirmwareVersion2() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] getWhitelistedFirmwareVersions() {
|
||||
return AmazfitBipFirmwareInfo.getWhitelistedVersions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFirmwareGenerallyCompatibleWith(GBDevice device) {
|
||||
return firmwareInfo.isGenerallyCompatibleWith(device);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleFirmware() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected void determineFirmwareInfo(byte[] wholeFirmwareBytes) {
|
||||
firmwareInfo = new AmazfitBipFirmwareInfo(wholeFirmwareBytes);
|
||||
if (!firmwareInfo.isHeaderValid()) {
|
||||
throw new IllegalArgumentException("Not a an Amazifit Bip firmware");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkValid() throws IllegalArgumentException {
|
||||
firmwareInfo.checkValid();
|
||||
}
|
||||
|
||||
public AmazfitBipFirmwareInfo getFirmwareInfo() {
|
||||
return firmwareInfo;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/* Copyright (C) 2015-2017 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.devices.amazfitbip;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.AbstractMiBandFWInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
class AmazfitBipFWInstallHandler extends AbstractMiBandFWInstallHandler {
|
||||
AmazfitBipFWInstallHandler(Uri uri, Context context) {
|
||||
super(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractMiBandFWHelper createHelper(Uri uri, Context context) throws IOException {
|
||||
return new AmazfitBipFWHelper(uri, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedDeviceType(GBDevice device) {
|
||||
return device.getType() == DeviceType.AMAZFITBIP;
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/* Copyright (C) 2017 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.amazfitbip;
|
||||
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
|
||||
public class AmazfitBipIcon {
|
||||
// icons which are unsure which app they are for are suffixed with _NN
|
||||
public static final int CHAT = 0;
|
||||
public static final int PENGUIN_1 = 1;
|
||||
public static final int MI_CHAT_2 = 2;
|
||||
public static final int FACEBOOK = 3;
|
||||
public static final int TWITTER = 4;
|
||||
public static final int MI_APP_5 = 5;
|
||||
public static final int SNAPCHAT = 6;
|
||||
public static final int WHATSAPP = 7;
|
||||
public static final int RED_WHITE_FIRE_8 = 8;
|
||||
public static final int CHINESE_9 = 9;
|
||||
public static final int ALARM_CLOCK = 10;
|
||||
public static final int APP_11 = 11;
|
||||
public static final int CAMERA_12 = 12;
|
||||
public static final int CHAT_BLUE_13 = 13;
|
||||
public static final int COW_14 = 14;
|
||||
public static final int CHINESE_15 = 15;
|
||||
public static final int CHINESE_16 = 16;
|
||||
public static final int STAR_17 = 17;
|
||||
public static final int APP_18 = 18;
|
||||
public static final int CHINESE_19 = 19;
|
||||
public static final int CHINESE_20 = 20;
|
||||
public static final int CALENDAR = 21;
|
||||
public static final int FACEBOOK_MESSENGER = 22;
|
||||
public static final int WHATSAPP_CALL_23 = 23;
|
||||
public static final int LINE = 24;
|
||||
public static final int TELEGRAM = 25;
|
||||
public static final int KAKAOTALK = 26;
|
||||
public static final int SKYPE = 27;
|
||||
public static final int VKONTAKTE = 28;
|
||||
public static final int POKEMONGO = 29;
|
||||
public static final int HANGOUTS = 30;
|
||||
public static final int MI_31 = 31;
|
||||
public static final int CHINESE_32 = 32;
|
||||
public static final int CHINESE_33 = 33;
|
||||
public static final int EMAIL = 34;
|
||||
public static final int WEATHER = 35;
|
||||
public static final int HR_WARNING_36 = 36;
|
||||
|
||||
|
||||
public static int mapToIconId(NotificationType type) {
|
||||
switch (type) {
|
||||
case UNKNOWN:
|
||||
return APP_11;
|
||||
case CONVERSATIONS:
|
||||
return CHAT;
|
||||
case GENERIC_EMAIL:
|
||||
return EMAIL;
|
||||
case GENERIC_NAVIGATION:
|
||||
return APP_11;
|
||||
case GENERIC_SMS:
|
||||
return CHAT;
|
||||
case GENERIC_CALENDAR:
|
||||
return CALENDAR;
|
||||
case FACEBOOK:
|
||||
return FACEBOOK;
|
||||
case FACEBOOK_MESSENGER:
|
||||
return FACEBOOK_MESSENGER;
|
||||
case RIOT:
|
||||
return CHAT;
|
||||
case SIGNAL:
|
||||
return CHAT_BLUE_13;
|
||||
case TWITTER:
|
||||
return TWITTER;
|
||||
case TELEGRAM:
|
||||
return TELEGRAM;
|
||||
case WHATSAPP:
|
||||
return WHATSAPP;
|
||||
case GENERIC_ALARM_CLOCK:
|
||||
return ALARM_CLOCK;
|
||||
}
|
||||
return APP_11;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/* Copyright (C) 2015-2017 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.amazfitbip;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class AmazfitBipService {
|
||||
public static final UUID UUID_CHARACTERISTIC_WEATHER = UUID.fromString("0000000e-0000-3512-2118-0009af100700");
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
/* Copyright (C) 2017 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.amazfitbip;
|
||||
|
||||
|
||||
public class AmazfitBipWeatherConditions {
|
||||
public static final byte CLEAR_SKY = 0;
|
||||
public static final byte SCATTERED_CLOUDS = 1;
|
||||
public static final byte CLOUDY = 2;
|
||||
public static final byte RAIN_WITH_SUN = 3;
|
||||
public static final byte THUNDERSTORM = 4;
|
||||
public static final byte HAIL = 5;
|
||||
public static final byte RAIN_AND_SNOW = 6;
|
||||
public static final byte LIGHT_RAIN = 7;
|
||||
public static final byte MEDIUM_RAIN = 8;
|
||||
public static final byte HEAVY_RAIN = 9;
|
||||
public static final byte EXTREME_RAIN = 10;
|
||||
public static final byte SUPER_EXTREME_RAIN = 11;
|
||||
public static final byte TORRENTIAL_RAIN = 12;
|
||||
public static final byte SNOW_AND_SUN = 13;
|
||||
public static final byte LIGHT_SNOW = 14;
|
||||
public static final byte MEDIUM_SNOW = 15;
|
||||
public static final byte HEAVY_SNOW = 16;
|
||||
public static final byte EXTREME_SNOW = 17;
|
||||
public static final byte MIST = 18;
|
||||
public static final byte DRIZZLE = 19;
|
||||
public static final byte WIND_AND_RAIN = 20;
|
||||
// 21- various types of rain
|
||||
|
||||
public static byte mapToAmazfitBipWeatherCode(int openWeatherMapCondition) {
|
||||
// openweathermap.org conditions:
|
||||
// http://openweathermap.org/weather-conditions
|
||||
switch (openWeatherMapCondition) {
|
||||
//Group 2xx: Thunderstorm
|
||||
case 200: //thunderstorm with light rain: //11d
|
||||
case 201: //thunderstorm with rain: //11d
|
||||
case 202: //thunderstorm with heavy rain: //11d
|
||||
case 210: //light thunderstorm:: //11d
|
||||
case 211: //thunderstorm: //11d
|
||||
case 230: //thunderstorm with light drizzle: //11d
|
||||
case 231: //thunderstorm with drizzle: //11d
|
||||
case 232: //thunderstorm with heavy drizzle: //11d
|
||||
case 212: //heavy thunderstorm: //11d
|
||||
case 221: //ragged thunderstorm: //11d
|
||||
return THUNDERSTORM;
|
||||
//Group 3xx: Drizzle
|
||||
case 300: //light intensity drizzle: //09d
|
||||
case 301: //drizzle: //09d
|
||||
case 302: //heavy intensity drizzle: //09d
|
||||
case 310: //light intensity drizzle rain: //09d
|
||||
case 311: //drizzle rain: //09d
|
||||
case 312: //heavy intensity drizzle rain: //09d
|
||||
case 313: //shower rain and drizzle: //09d
|
||||
case 314: //heavy shower rain and drizzle: //09d
|
||||
case 321: //shower drizzle: //09d
|
||||
return DRIZZLE;
|
||||
//Group 5xx: Rain
|
||||
case 500: //light rain: //10d
|
||||
return LIGHT_RAIN;
|
||||
case 501: //moderate rain: //10d
|
||||
return MEDIUM_RAIN;
|
||||
case 502: //heavy intensity rain: //10d
|
||||
return HEAVY_RAIN;
|
||||
case 503: //very heavy rain: //10d
|
||||
return EXTREME_RAIN;
|
||||
case 504: //extreme rain: //10d
|
||||
return TORRENTIAL_RAIN;
|
||||
case 511: //freezing rain: //13d
|
||||
return MEDIUM_RAIN;
|
||||
case 520: //light intensity shower rain: //09d
|
||||
return LIGHT_RAIN;
|
||||
case 521: //shower rain: //09d
|
||||
return MEDIUM_RAIN;
|
||||
case 522: //heavy intensity shower rain: //09d
|
||||
return HEAVY_RAIN;
|
||||
case 531: //ragged shower rain: //09d
|
||||
return MEDIUM_RAIN;
|
||||
//Group 6xx: Snow
|
||||
case 600: //light snow: //[[file:13d.png]]
|
||||
return LIGHT_SNOW;
|
||||
case 601: //snow: //[[file:13d.png]]
|
||||
return MEDIUM_SNOW;
|
||||
case 602: //heavy snow: //[[file:13d.png]]
|
||||
return HEAVY_SNOW;
|
||||
case 611: //sleet: //[[file:13d.png]]
|
||||
case 612: //shower sleet: //[[file:13d.png]]
|
||||
case 615: //light rain and snow: //[[file:13d.png]]
|
||||
case 616: //rain and snow: //[[file:13d.png]]
|
||||
case 620: //light shower snow: //[[file:13d.png]]
|
||||
case 621: //shower snow: //[[file:13d.png]]
|
||||
case 622: //heavy shower snow: //[[file:13d.png]]
|
||||
return RAIN_AND_SNOW;
|
||||
|
||||
//Group 7xx: Atmosphere
|
||||
case 701: //mist: //[[file:50d.png]]
|
||||
case 711: //smoke: //[[file:50d.png]]
|
||||
case 721: //haze: //[[file:50d.png]]
|
||||
case 731: //sandcase dust whirls: //[[file:50d.png]]
|
||||
case 741: //fog: //[[file:50d.png]]
|
||||
case 751: //sand: //[[file:50d.png]]
|
||||
case 761: //dust: //[[file:50d.png]]
|
||||
case 762: //volcanic ash: //[[file:50d.png]]
|
||||
case 771: //squalls: //[[file:50d.png]]
|
||||
return MIST;
|
||||
case 781: //tornado: //[[file:50d.png]]
|
||||
case 900: //tornado
|
||||
return WIND_AND_RAIN;
|
||||
//Group 800: Clear
|
||||
case 800: //clear sky: //[[file:01d.png]] [[file:01n.png]]
|
||||
return CLEAR_SKY;
|
||||
//Group 80x: Clouds
|
||||
case 801: //few clouds: //[[file:02d.png]] [[file:02n.png]]
|
||||
case 802: //scattered clouds: //[[file:03d.png]] [[file:03d.png]]
|
||||
case 803: //broken clouds: //[[file:04d.png]] [[file:03d.png]]
|
||||
return SCATTERED_CLOUDS;
|
||||
case 804: //overcast clouds: //[[file:04d.png]] [[file:04d.png]]
|
||||
return CLOUDY;
|
||||
//Group 90x: Extreme
|
||||
case 901: //tropical storm
|
||||
return WIND_AND_RAIN;
|
||||
case 903: //cold
|
||||
case 904: //hot
|
||||
case 905: //windy
|
||||
return 0;
|
||||
case 906: //hail
|
||||
return HAIL;
|
||||
//Group 9xx: Additional
|
||||
case 951: //calm
|
||||
case 952: //light breeze
|
||||
case 953: //gentle breeze
|
||||
case 954: //moderate breeze
|
||||
case 955: //fresh breeze
|
||||
case 956: //strong breeze
|
||||
case 957: //high windcase near gale
|
||||
case 958: //gale
|
||||
case 959: //severe gale
|
||||
case 960: //storm
|
||||
case 961: //violent storm
|
||||
case 902: //hurricane
|
||||
case 962: //hurricane
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -79,7 +79,6 @@ public final class HPlusConstants {
|
||||
public static final byte ARG_FINDME_ON = 0x01;
|
||||
public static final byte ARG_FINDME_OFF = 0x02;
|
||||
|
||||
public static final byte CMD_GET_VERSION = 0x17;
|
||||
public static final byte CMD_SET_END = 0x4f;
|
||||
public static final byte CMD_SET_INCOMING_CALL_NUMBER = 0x23;
|
||||
public static final byte CMD_SET_ALLDAY_HRM = 0x35;
|
||||
@ -89,7 +88,8 @@ public final class HPlusConstants {
|
||||
public static final byte CMD_SET_SIT_INTERVAL = 0x51;
|
||||
public static final byte CMD_SET_HEARTRATE_STATE = 0x32;
|
||||
|
||||
//Actions to device
|
||||
//GET messages
|
||||
public static final byte CMD_GET_VERSION = 0x17;
|
||||
public static final byte CMD_GET_ACTIVE_DAY = 0x27;
|
||||
public static final byte CMD_GET_DAY_DATA = 0x15;
|
||||
public static final byte CMD_GET_SLEEP = 0x19;
|
||||
@ -122,7 +122,7 @@ public final class HPlusConstants {
|
||||
public static final byte DATA_SLEEP = 0x1A;
|
||||
public static final byte DATA_VERSION = 0x18;
|
||||
public static final byte DATA_VERSION1 = 0x2E;
|
||||
|
||||
public static final byte DATA_DAY_UNKNOWN = 0x52; //To be defined
|
||||
public static final byte DATA_UNKNOWN = 0x4d;
|
||||
|
||||
public static final String PREF_HPLUS_SCREENTIME = "hplus_screentime";
|
||||
|
@ -92,6 +92,11 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.HPLUS;
|
||||
@ -147,11 +152,6 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
return R.string.tap_connected_device_for_activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Zeblaze";
|
||||
@ -289,6 +289,6 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
||||
}
|
||||
|
||||
public static boolean getUnicodeSupport(String address){
|
||||
return (prefs.getBoolean(HPlusConstants.PREF_HPLUS_UNICODE, false));
|
||||
return (prefs.getBoolean(HPlusConstants.PREF_HPLUS_UNICODE + "_" + address, false));
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ package nodomain.freeyourgadget.gadgetbridge.devices.hplus;
|
||||
*/
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
@ -141,6 +142,58 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider<HPlusHealt
|
||||
|
||||
List<HPlusHealthActivityOverlay> overlayRecords = qb.build().list();
|
||||
|
||||
|
||||
|
||||
//Apply Overlays
|
||||
for (HPlusHealthActivityOverlay overlay : overlayRecords) {
|
||||
|
||||
//Create fake events to improve activity counters if there are no events around the overlay
|
||||
//timestamp boundaries
|
||||
//Insert one before, one at the beginning, one at the end, and one 1s after.
|
||||
insertVirtualItem(samples, Math.max(overlay.getTimestampFrom() - 1, timestamp_from), overlay.getDeviceId(), overlay.getUserId());
|
||||
insertVirtualItem(samples, Math.max(overlay.getTimestampFrom(), timestamp_from), overlay.getDeviceId(), overlay.getUserId());
|
||||
insertVirtualItem(samples, Math.min(overlay.getTimestampTo() - 1, timestamp_to - 1), overlay.getDeviceId(), overlay.getUserId());
|
||||
insertVirtualItem(samples, Math.min(overlay.getTimestampTo(), timestamp_to), overlay.getDeviceId(), overlay.getUserId());
|
||||
}
|
||||
|
||||
Collections.sort(samples, new Comparator<HPlusHealthActivitySample>() {
|
||||
public int compare(HPlusHealthActivitySample one, HPlusHealthActivitySample other) {
|
||||
return one.getTimestamp() - other.getTimestamp();
|
||||
}
|
||||
});
|
||||
|
||||
//Apply Overlays
|
||||
for (HPlusHealthActivityOverlay overlay : overlayRecords) {
|
||||
|
||||
long nonSleepTimeEnd = 0;
|
||||
for (HPlusHealthActivitySample sample : samples) {
|
||||
if (sample.getRawKind() == ActivityKind.TYPE_NOT_WORN)
|
||||
continue;
|
||||
|
||||
if (sample.getTimestamp() >= overlay.getTimestampFrom() && sample.getTimestamp() < overlay.getTimestampTo()) {
|
||||
if (overlay.getRawKind() == ActivityKind.TYPE_NOT_WORN || overlay.getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP || overlay.getRawKind() == ActivityKind.TYPE_DEEP_SLEEP) {
|
||||
if (sample.getRawKind() == HPlusDataRecord.TYPE_DAY_SLOT && sample.getSteps() > 0){
|
||||
nonSleepTimeEnd = sample.getTimestamp() + 10 * 60; // 10 minutes
|
||||
continue;
|
||||
}else if(sample.getRawKind() == HPlusDataRecord.TYPE_REALTIME && sample.getTimestamp() <= nonSleepTimeEnd){
|
||||
continue;
|
||||
}
|
||||
|
||||
if (overlay.getRawKind() == ActivityKind.TYPE_NOT_WORN)
|
||||
sample.setHeartRate(0);
|
||||
|
||||
if (sample.getRawKind() != ActivityKind.TYPE_NOT_WORN)
|
||||
sample.setRawKind(overlay.getRawKind());
|
||||
|
||||
sample.setRawIntensity(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Fix Step counters
|
||||
//Todays sample steps will come from the Day Slots messages
|
||||
//Historical steps will be provided by Day Summaries messages
|
||||
//This will allow both week and current day results to be consistent
|
||||
@ -180,35 +233,8 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider<HPlusHealt
|
||||
if(lastSample != null)
|
||||
lastSample.setSteps(Math.max(stepsTodayCount, stepsTodayMax));
|
||||
|
||||
for (HPlusHealthActivityOverlay overlay : overlayRecords) {
|
||||
|
||||
//Create fake events to improve activity counters if there are no events around the overlay
|
||||
//timestamp boundaries
|
||||
//Insert one before, one at the beginning, one at the end, and one 1s after.
|
||||
insertVirtualItem(samples, Math.max(overlay.getTimestampFrom() - 1, timestamp_from), overlay.getDeviceId(), overlay.getUserId());
|
||||
insertVirtualItem(samples, Math.max(overlay.getTimestampFrom(), timestamp_from), overlay.getDeviceId(), overlay.getUserId());
|
||||
insertVirtualItem(samples, Math.min(overlay.getTimestampTo() - 1, timestamp_to - 1), overlay.getDeviceId(), overlay.getUserId());
|
||||
insertVirtualItem(samples, Math.min(overlay.getTimestampTo(), timestamp_to), overlay.getDeviceId(), overlay.getUserId());
|
||||
|
||||
for (HPlusHealthActivitySample sample : samples) {
|
||||
|
||||
if (sample.getTimestamp() >= overlay.getTimestampFrom() && sample.getTimestamp() < overlay.getTimestampTo()) {
|
||||
if(overlay.getRawKind() == ActivityKind.TYPE_LIGHT_SLEEP || overlay.getRawKind() == ActivityKind.TYPE_DEEP_SLEEP)
|
||||
sample.setRawIntensity(10);
|
||||
|
||||
sample.setRawKind(overlay.getRawKind());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
detachFromSession();
|
||||
|
||||
Collections.sort(samples, new Comparator<HPlusHealthActivitySample>() {
|
||||
public int compare(HPlusHealthActivitySample one, HPlusHealthActivitySample other) {
|
||||
return one.getTimestamp() - other.getTimestamp();
|
||||
}
|
||||
});
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
|
@ -99,12 +99,6 @@ public class LiveviewCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
//TODO: changeme
|
||||
return R.string.tap_connected_device_for_activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Sony Ericsson";
|
||||
@ -125,6 +119,11 @@ public class LiveviewCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
// nothing to delete, yet
|
||||
|
@ -82,7 +82,7 @@ public abstract class AbstractMiBandFWInstallHandler implements InstallHandler {
|
||||
}
|
||||
|
||||
GenericItem fwItem = new GenericItem(mContext.getString(R.string.miband_installhandler_miband_firmware, helper.getHumanFirmwareVersion()));
|
||||
fwItem.setIcon(R.drawable.ic_device_miband);
|
||||
fwItem.setIcon(device.getType().getIcon());
|
||||
|
||||
if (!helper.isFirmwareGenerallyCompatibleWith(device)) {
|
||||
fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_incompatible_version));
|
||||
|
@ -0,0 +1,23 @@
|
||||
/* Copyright (C) 2017 José Rebelo
|
||||
|
||||
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.miband;
|
||||
|
||||
public enum DoNotDisturb {
|
||||
OFF,
|
||||
AUTOMATIC,
|
||||
SCHEDULED
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer, José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -28,8 +28,12 @@ import android.support.annotation.NonNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -117,6 +121,92 @@ public class MiBand2Coordinator extends MiBandCoordinator {
|
||||
return prefs.getBoolean(MiBandConst.PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT, true);
|
||||
}
|
||||
|
||||
public static Set<String> getDisplayItems() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getStringSet(MiBandConst.PREF_MI2_DISPLAY_ITEMS, null);
|
||||
}
|
||||
|
||||
public static boolean getGoalNotification() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getBoolean(MiBandConst.PREF_MI2_GOAL_NOTIFICATION, false);
|
||||
}
|
||||
|
||||
public static boolean getRotateWristToSwitchInfo() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getBoolean(MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO, false);
|
||||
}
|
||||
|
||||
public static boolean getInactivityWarnings() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getBoolean(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS, false);
|
||||
}
|
||||
|
||||
public static int getInactivityWarningsThreshold() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getInt(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD, 60);
|
||||
}
|
||||
|
||||
public static boolean getInactivityWarningsDnd() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
return prefs.getBoolean(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND, false);
|
||||
}
|
||||
|
||||
public static Date getInactivityWarningsStart() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_START, "06:00");
|
||||
}
|
||||
|
||||
public static Date getInactivityWarningsEnd() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_END, "22:00");
|
||||
}
|
||||
|
||||
public static Date getInactivityWarningsDndStart() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND_START, "12:00");
|
||||
}
|
||||
|
||||
public static Date getInactivityWarningsDndEnd() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND_END, "14:00");
|
||||
}
|
||||
|
||||
public static Date getDoNotDisturbStart() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_DO_NOT_DISTURB_START, "01:00");
|
||||
}
|
||||
|
||||
public static Date getDoNotDisturbEnd() {
|
||||
return getTimePreference(MiBandConst.PREF_MI2_DO_NOT_DISTURB_END, "06:00");
|
||||
}
|
||||
|
||||
public static Date getTimePreference(String key, String defaultValue) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
String time = prefs.getString(key, defaultValue);
|
||||
|
||||
DateFormat df = new SimpleDateFormat("HH:mm");
|
||||
try {
|
||||
return df.parse(time);
|
||||
} catch(Exception e) {
|
||||
LOG.error("Unexpected exception in MiBand2Coordinator.getTime: " + e.getMessage());
|
||||
}
|
||||
|
||||
return new Date();
|
||||
}
|
||||
|
||||
public static DoNotDisturb getDoNotDisturb(Context context) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
|
||||
String dndOff = context.getString(R.string.p_off);
|
||||
String dndAutomatic = context.getString(R.string.p_automatic);
|
||||
String dndScheduled = context.getString(R.string.p_scheduled);
|
||||
|
||||
String pref = prefs.getString(MiBandConst.PREF_MI2_DO_NOT_DISTURB, dndOff);
|
||||
|
||||
if (dndAutomatic.equals(pref)) {
|
||||
return DoNotDisturb.AUTOMATIC;
|
||||
} else if (dndScheduled.equals(pref)) {
|
||||
return DoNotDisturb.SCHEDULED;
|
||||
}
|
||||
|
||||
return DoNotDisturb.OFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
MiBand2FWInstallHandler handler = new MiBand2FWInstallHandler(uri, context);
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer, JohnnySun, Uwe Hermann
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, JohnnySun,
|
||||
José Rebelo, Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -53,7 +54,6 @@ public class MiBand2Service {
|
||||
public static final int ALERT_LEVEL_MESSAGE = 1;
|
||||
public static final int ALERT_LEVEL_PHONE_CALL = 2;
|
||||
public static final int ALERT_LEVEL_VIBRATE_ONLY = 3;
|
||||
public static final int ALERT_LEVEL_CUSTOM = 0xfa; // followed by another uin8 to select the actual icon
|
||||
|
||||
// set metric distance
|
||||
// set 12 hour time mode
|
||||
@ -145,6 +145,19 @@ public class MiBand2Service {
|
||||
|
||||
public static final byte ICON_HIGH_PRIORITY = 0x7;
|
||||
|
||||
public static byte ENDPOINT_DISPLAY_ITEMS = 0x0a;
|
||||
|
||||
public static byte DISPLAY_ITEM_BIT_CLOCK = 0x01;
|
||||
public static byte DISPLAY_ITEM_BIT_STEPS = 0x02;
|
||||
public static byte DISPLAY_ITEM_BIT_DISTANCE = 0x04;
|
||||
public static byte DISPLAY_ITEM_BIT_CALORIES= 0x08;
|
||||
public static byte DISPLAY_ITEM_BIT_HEART_RATE = 0x10;
|
||||
public static byte DISPLAY_ITEM_BIT_BATTERY = 0x20;
|
||||
|
||||
// Second byte must be a bitwise OR combination of the above
|
||||
// The clock can't be disabled
|
||||
public static int SCREEN_CHANGE_BYTE = 1;
|
||||
public static final byte[] COMMAND_CHANGE_SCREENS = new byte[]{ENDPOINT_DISPLAY_ITEMS, DISPLAY_ITEM_BIT_CLOCK, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
|
||||
|
||||
public static byte ENDPOINT_DISPLAY = 0x06;
|
||||
|
||||
@ -154,9 +167,42 @@ public class MiBand2Service {
|
||||
public static final byte[] DATEFORMAT_TIME_24_HOURS = new byte[] {ENDPOINT_DISPLAY, 0x02, 0x0, 0x1 };
|
||||
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_ENABLE_GOAL_NOTIFICATION = new byte[]{ENDPOINT_DISPLAY, 0x06, 0x00, 0x01};
|
||||
public static final byte[] COMMAND_DISABLE_GOAL_NOTIFICATION = new byte[]{ENDPOINT_DISPLAY, 0x06, 0x00, 0x00};
|
||||
public static final byte[] COMMAND_ENABLE_ROTATE_WRIST_TO_SWITCH_INFO = new byte[]{ENDPOINT_DISPLAY, 0x0d, 0x00, 0x01};
|
||||
public static final byte[] COMMAND_DISABLE_ROTATE_WRIST_TO_SWITCH_INFO = new byte[]{ENDPOINT_DISPLAY, 0x0d, 0x00, 0x00};
|
||||
public static final byte[] COMMAND_ENABLE_DISPLAY_CALLER = new byte[]{ENDPOINT_DISPLAY, 0x10, 0x00, 0x00, 0x01};
|
||||
public static final byte[] COMMAND_DISABLE_DISPLAY_CALLER = new byte[]{ENDPOINT_DISPLAY, 0x10, 0x00, 0x00, 0x00};
|
||||
public static final byte[] DISPLAY_XXX = new byte[] {ENDPOINT_DISPLAY, 0x03, 0x0, 0x0 };
|
||||
public static final byte[] DISPLAY_YYY = new byte[] {ENDPOINT_DISPLAY, 0x10, 0x0, 0x1, 0x1 };
|
||||
|
||||
// The third byte controls the threshold, in minutes
|
||||
// The last 8 bytes represent 2 separate time intervals for the inactivity warnings
|
||||
// If there is no do not disturb interval, the last 4 bytes (the second interval) are 0
|
||||
// and only the first interval of the command is used
|
||||
public static int INACTIVITY_WARNINGS_THRESHOLD = 2;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_1_START_HOURS = 4;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_1_START_MINUTES = 5;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS = 6;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES = 7;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_2_START_HOURS = 8;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_2_START_MINUTES = 9;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_2_END_HOURS = 10;
|
||||
public static int INACTIVITY_WARNINGS_INTERVAL_2_END_MINUTES = 11;
|
||||
public static final byte[] COMMAND_ENABLE_INACTIVITY_WARNINGS = new byte[] { 0x08, 0x01, 0x3c, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
public static final byte[] COMMAND_DISABLE_INACTIVITY_WARNINGS = new byte[] { 0x08, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
public static byte ENDPOINT_DND = 0x09;
|
||||
|
||||
public static final byte[] COMMAND_DO_NOT_DISTURB_AUTOMATIC = new byte[] { ENDPOINT_DND, (byte) 0x83 };
|
||||
public static final byte[] COMMAND_DO_NOT_DISTURB_OFF = new byte[] { ENDPOINT_DND, (byte) 0x82 };
|
||||
public static final byte[] COMMAND_DO_NOT_DISTURB_SCHEDULED = new byte[] { ENDPOINT_DND, (byte) 0x81, 0x01, 0x00, 0x06, 0x00 };
|
||||
// The 4 last bytes set the start and end time in 24h format
|
||||
public static byte DND_BYTE_START_HOURS = 2;
|
||||
public static byte DND_BYTE_START_MINUTES = 3;
|
||||
public static byte DND_BYTE_END_HOURS = 4;
|
||||
public static byte DND_BYTE_END_MINUTES = 5;
|
||||
|
||||
public static final byte RESPONSE = 0x10;
|
||||
|
||||
public static final byte SUCCESS = 0x01;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Christian
|
||||
Fischer, Daniele Gobbetti, Szymon Tomasz Stefanek
|
||||
Fischer, Daniele Gobbetti, José Rebelo, Szymon Tomasz Stefanek
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -35,8 +35,30 @@ public final class MiBandConst {
|
||||
public static final String PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION = "mi_hr_sleep_detection";
|
||||
public static final String PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS = "mi_device_time_offset_hours";
|
||||
public static final String PREF_MI2_DATEFORMAT = "mi2_dateformat";
|
||||
public static final String PREF_MI2_GOAL_NOTIFICATION = "mi2_goal_notification";
|
||||
public static final String PREF_MI2_DISPLAY_ITEMS = "mi2_display_items";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_CLOCK = "clock";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_STEPS = "steps";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_DISTANCE = "distance";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_CALORIES = "calories";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_HEART_RATE = "heart_rate";
|
||||
public static final String PREF_MI2_DISPLAY_ITEM_BATTERY = "battery";
|
||||
public static final String PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT = "mi2_activate_display_on_lift_wrist";
|
||||
public static final String PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO = "mi2_rotate_wrist_to_switch_info";
|
||||
public static final String PREF_MI2_ENABLE_TEXT_NOTIFICATIONS = "mi2_enable_text_notifications";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB = "mi2_do_not_disturb";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_OFF = "off";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_AUTOMATIC = "automatic";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_SCHEDULED = "scheduled";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_START = "mi2_do_not_disturb_start";
|
||||
public static final String PREF_MI2_DO_NOT_DISTURB_END = "mi2_do_not_disturb_end";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS = "mi2_inactivity_warnings";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD = "mi2_inactivity_warnings_threshold";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_START = "mi2_inactivity_warnings_start";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_END = "mi2_inactivity_warnings_end";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_DND = "mi2_inactivity_warnings_dnd";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_DND_START = "mi2_inactivity_warnings_dnd_start";
|
||||
public static final String PREF_MI2_INACTIVITY_WARNINGS_DND_END = "mi2_inactivity_warnings_dnd_end";
|
||||
public static final String PREF_MIBAND_SETUP_BT_PAIRING = "mi_setup_bt_pairing";
|
||||
|
||||
|
||||
|
@ -151,11 +151,6 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
return R.string.tap_connected_device_for_activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Xiaomi";
|
||||
@ -176,6 +171,11 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean hasValidUserInfo() {
|
||||
String dummyMacAddress = MiBandService.MAC_ADDRESS_FILTER_1_1A + ":00:00:00";
|
||||
try {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Christian
|
||||
Fischer, Daniele Gobbetti, Szymon Tomasz Stefanek
|
||||
Fischer, Daniele Gobbetti, José Rebelo, Szymon Tomasz Stefanek
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -34,12 +34,28 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_ALARM_CLOCK;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_INCOMING_CALL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DATEFORMAT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DISPLAY_ITEMS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_OFF;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_SCHEDULED;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DO_NOT_DISTURB_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ENABLE_TEXT_NOTIFICATIONS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_GOAL_NOTIFICATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_END;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ADDRESS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR;
|
||||
@ -57,6 +73,8 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
|
||||
addTryListeners();
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
|
||||
final Preference enableHeartrateSleepSupport = findPreference(PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION);
|
||||
enableHeartrateSleepSupport.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
@ -66,6 +84,20 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
}
|
||||
});
|
||||
|
||||
final Preference goalNotification = findPreference(PREF_MI2_GOAL_NOTIFICATION);
|
||||
goalNotification.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_GOAL_NOTIFICATION);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference setDateFormat = findPreference(PREF_MI2_DATEFORMAT);
|
||||
setDateFormat.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
@ -80,6 +112,20 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
}
|
||||
});
|
||||
|
||||
final Preference displayPages = findPreference(PREF_MI2_DISPLAY_ITEMS);
|
||||
displayPages.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference activateDisplayOnLift = findPreference(PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT);
|
||||
activateDisplayOnLift.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
@ -94,6 +140,170 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
}
|
||||
});
|
||||
|
||||
final Preference rotateWristCycleInfo = findPreference(PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO);
|
||||
rotateWristCycleInfo.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarnings = findPreference(PREF_MI2_INACTIVITY_WARNINGS);
|
||||
inactivityWarnings.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarningsThreshold = findPreference(PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD);
|
||||
inactivityWarningsThreshold.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarningsStart = findPreference(PREF_MI2_INACTIVITY_WARNINGS_START);
|
||||
inactivityWarningsStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarningsEnd = findPreference(PREF_MI2_INACTIVITY_WARNINGS_END);
|
||||
inactivityWarningsEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarningsDnd = findPreference(PREF_MI2_INACTIVITY_WARNINGS_DND);
|
||||
inactivityWarningsDnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS_DND);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarningsDndStart = findPreference(PREF_MI2_INACTIVITY_WARNINGS_DND_START);
|
||||
inactivityWarningsDndStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS_DND_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference inactivityWarningsDndEnd = findPreference(PREF_MI2_INACTIVITY_WARNINGS_DND_END);
|
||||
inactivityWarningsDndEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_INACTIVITY_WARNINGS_DND_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
String doNotDisturbState = prefs.getString(MiBandConst.PREF_MI2_DO_NOT_DISTURB, PREF_MI2_DO_NOT_DISTURB_OFF);
|
||||
boolean doNotDisturbScheduled = doNotDisturbState.equals(PREF_MI2_DO_NOT_DISTURB_SCHEDULED);
|
||||
|
||||
final Preference doNotDisturbStart = findPreference(PREF_MI2_DO_NOT_DISTURB_START);
|
||||
doNotDisturbStart.setEnabled(doNotDisturbScheduled);
|
||||
doNotDisturbStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DO_NOT_DISTURB_START);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference doNotDisturbEnd = findPreference(PREF_MI2_DO_NOT_DISTURB_END);
|
||||
doNotDisturbEnd.setEnabled(doNotDisturbScheduled);
|
||||
doNotDisturbEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DO_NOT_DISTURB_END);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference doNotDisturb = findPreference(PREF_MI2_DO_NOT_DISTURB);
|
||||
doNotDisturb.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
final boolean scheduled = PREF_MI2_DO_NOT_DISTURB_SCHEDULED.equals(newVal.toString());
|
||||
|
||||
doNotDisturbStart.setEnabled(scheduled);
|
||||
doNotDisturbEnd.setEnabled(scheduled);
|
||||
|
||||
invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DO_NOT_DISTURB);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference fitnessGoal = findPreference(ActivityUser.PREF_USER_STEPS_GOAL);
|
||||
fitnessGoal.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
@ -165,6 +375,7 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
prefKeys.add(PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR);
|
||||
prefKeys.add(PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS);
|
||||
prefKeys.add(PREF_MI2_ENABLE_TEXT_NOTIFICATIONS);
|
||||
prefKeys.add(PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD);
|
||||
prefKeys.add(getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_ALARM_CLOCK));
|
||||
prefKeys.add(getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_INCOMING_CALL));
|
||||
|
||||
|
@ -137,11 +137,6 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
|
||||
return PebbleUtils.hasHRM(device.getModel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
return R.string.tap_connected_device_for_app_mananger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Pebble";
|
||||
@ -161,4 +156,9 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
|
||||
public boolean supportsCalendarEvents() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -37,11 +37,13 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
public class PebbleHealthSampleProvider extends AbstractSampleProvider<PebbleHealthActivitySample> {
|
||||
public static final int TYPE_LIGHT_SLEEP = 1;
|
||||
public static final int TYPE_DEEP_SLEEP = 2;
|
||||
public static final int TYPE_LIGHT_NAP = 3; //probably
|
||||
public static final int TYPE_DEEP_NAP = 4; //probably
|
||||
public static final int TYPE_WALK = 5; //probably
|
||||
public static final int TYPE_LIGHT_NAP = 3;
|
||||
public static final int TYPE_DEEP_NAP = 4;
|
||||
public static final int TYPE_WALK = 5;
|
||||
public static final int TYPE_RUN = 6;
|
||||
public static final int TYPE_ACTIVITY = -1;
|
||||
|
||||
|
||||
protected final float movementDivisor = 8000f;
|
||||
|
||||
public PebbleHealthSampleProvider(GBDevice device, DaoSession session) {
|
||||
@ -114,6 +116,8 @@ public class PebbleHealthSampleProvider extends AbstractSampleProvider<PebbleHea
|
||||
case TYPE_LIGHT_SLEEP:
|
||||
return ActivityKind.TYPE_LIGHT_SLEEP;
|
||||
case TYPE_ACTIVITY:
|
||||
case TYPE_WALK:
|
||||
case TYPE_RUN:
|
||||
return ActivityKind.TYPE_ACTIVITY;
|
||||
default:
|
||||
return ActivityKind.TYPE_UNKNOWN;
|
||||
|
@ -100,11 +100,6 @@ public class VibratissimoCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTapString() {
|
||||
return R.string.tap_connected_device_for_vibration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Amor AG";
|
||||
@ -125,6 +120,11 @@ public class VibratissimoCoordinator extends AbstractDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false; // hmmm well, it has a temperature sensor :D
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
// nothing to delete, yet
|
||||
|
@ -34,7 +34,6 @@ import java.util.List;
|
||||
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncState;
|
||||
|
@ -48,6 +48,12 @@ public class MusicPlaybackReceiver extends BroadcastReceiver {
|
||||
MusicStateSpec stateSpec = new MusicStateSpec(lastStateSpec);
|
||||
|
||||
Bundle incomingBundle = intent.getExtras();
|
||||
|
||||
if (incomingBundle == null) {
|
||||
LOG.warn("Not processing incoming null bundle.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (String key : incomingBundle.keySet()) {
|
||||
Object incoming = incomingBundle.get(key);
|
||||
if (incoming instanceof String && "artist".equals(key)) {
|
||||
|
@ -18,7 +18,6 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.externalevents;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
@ -30,17 +29,19 @@ import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.MediaSession;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.support.v7.app.NotificationCompat;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -76,7 +77,7 @@ public class NotificationListener extends NotificationListenerService {
|
||||
private LimitedQueue mActionLookup = new LimitedQueue(16);
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@SuppressLint("NewApi")
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
@ -102,7 +103,7 @@ public class NotificationListener extends NotificationListenerService {
|
||||
} else {
|
||||
// ACTION_MUTE
|
||||
LOG.info("going to mute " + sbn.getPackageName());
|
||||
GBApplication.addToBlacklist(sbn.getPackageName());
|
||||
GBApplication.addAppToBlacklist(sbn.getPackageName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,16 +178,8 @@ public class NotificationListener extends NotificationListenerService {
|
||||
|
||||
@Override
|
||||
public void onNotificationPosted(StatusBarNotification sbn) {
|
||||
/*
|
||||
* return early if DeviceCommunicationService is not running,
|
||||
* else the service would get started every time we get a notification.
|
||||
* unfortunately we cannot enable/disable NotificationListener at runtime like we do with
|
||||
* broadcast receivers because it seems to invalidate the permissions that are
|
||||
* necessary for NotificationListenerService
|
||||
*/
|
||||
if (!isServiceRunning()) {
|
||||
if (shouldIgnore(sbn))
|
||||
return;
|
||||
}
|
||||
|
||||
switch (GBApplication.getGrantedInterruptionFilter()) {
|
||||
case NotificationManager.INTERRUPTION_FILTER_ALL:
|
||||
@ -201,53 +194,8 @@ public class NotificationListener extends NotificationListenerService {
|
||||
|
||||
String source = sbn.getPackageName();
|
||||
Notification notification = sbn.getNotification();
|
||||
|
||||
if (handleMediaSessionNotification(notification))
|
||||
return;
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
|
||||
PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
if (powermanager.isScreenOn()) {
|
||||
// LOG.info("Not forwarding notification, screen seems to be on and settings do not allow this");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) {
|
||||
// LOG.info("Not forwarding notification, FLAG_ONGOING_EVENT is set. Notification flags: " + notification.flags);
|
||||
return;
|
||||
}
|
||||
|
||||
/* do not display messages from "android"
|
||||
* This includes keyboard selection message, usb connection messages, etc
|
||||
* Hope it does not filter out too much, we will see...
|
||||
*/
|
||||
|
||||
if (source.equals("android") ||
|
||||
source.equals("com.android.systemui") ||
|
||||
source.equals("com.android.dialer") ||
|
||||
source.equals("com.cyanogenmod.eleven")) {
|
||||
LOG.info("Not forwarding notification, is a system event");
|
||||
return;
|
||||
}
|
||||
|
||||
if (source.equals("com.moez.QKSMS") ||
|
||||
source.equals("com.android.mms") ||
|
||||
source.equals("com.sonyericsson.conversations") ||
|
||||
source.equals("com.android.messaging") ||
|
||||
source.equals("org.smssecure.smssecure")) {
|
||||
if (!"never".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (GBApplication.isBlacklisted(source)) {
|
||||
LOG.info("Not forwarding notification, application is blacklisted");
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationSpec notificationSpec = new NotificationSpec();
|
||||
notificationSpec.id = (int) sbn.getPostTime(); //FIMXE: a truly unique id would be better
|
||||
|
||||
// determinate Source App Name ("Label")
|
||||
PackageManager pm = getPackageManager();
|
||||
@ -266,10 +214,6 @@ public class NotificationListener extends NotificationListenerService {
|
||||
notificationSpec.type = AppNotificationType.getInstance().get(source);
|
||||
|
||||
if (source.startsWith("com.fsck.k9")) {
|
||||
// we dont want group summaries at all for k9
|
||||
if ((notification.flags & Notification.FLAG_GROUP_SUMMARY) == Notification.FLAG_GROUP_SUMMARY) {
|
||||
return;
|
||||
}
|
||||
preferBigText = true;
|
||||
}
|
||||
|
||||
@ -277,10 +221,9 @@ public class NotificationListener extends NotificationListenerService {
|
||||
notificationSpec.type = NotificationType.UNKNOWN;
|
||||
}
|
||||
|
||||
LOG.info("Processing notification from source " + source + " with flags: " + notification.flags);
|
||||
LOG.info("Processing notification " + notificationSpec.id + " from source " + source + " with flags: " + notification.flags);
|
||||
|
||||
dissectNotificationTo(notification, notificationSpec, preferBigText);
|
||||
notificationSpec.id = (int) sbn.getPostTime(); //FIMXE: a truly unique id would be better
|
||||
|
||||
// ignore Gadgetbridge's very own notifications, except for those from the debug screen
|
||||
if (getApplicationContext().getPackageName().equals(source)) {
|
||||
@ -292,11 +235,6 @@ public class NotificationListener extends NotificationListenerService {
|
||||
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(notification);
|
||||
List<NotificationCompat.Action> actions = wearableExtender.getActions();
|
||||
|
||||
if (actions.isEmpty() && (notification.flags & Notification.FLAG_GROUP_SUMMARY) == Notification.FLAG_GROUP_SUMMARY) { //this could cause #395 to come back
|
||||
LOG.info("Not forwarding notification, FLAG_GROUP_SUMMARY is set and no wearable action present. Notification flags: " + notification.flags);
|
||||
return;
|
||||
}
|
||||
|
||||
for (NotificationCompat.Action act : actions) {
|
||||
if (act != null && act.getRemoteInputs() != null) {
|
||||
LOG.info("found wearable action: " + act.getTitle() + " " + sbn.getTag());
|
||||
@ -306,11 +244,17 @@ public class NotificationListener extends NotificationListenerService {
|
||||
}
|
||||
}
|
||||
|
||||
if ((notificationSpec.flags & NotificationSpec.FLAG_WEARABLE_REPLY) == 0 && NotificationCompat.isGroupSummary(notification)) { //this could cause #395 to come back
|
||||
LOG.info("Not forwarding notification, FLAG_GROUP_SUMMARY is set and no wearable action present. Notification flags: " + notification.flags);
|
||||
return;
|
||||
}
|
||||
|
||||
GBApplication.deviceService().onNotification(notificationSpec);
|
||||
}
|
||||
|
||||
private void dissectNotificationTo(Notification notification, NotificationSpec notificationSpec, boolean preferBigText) {
|
||||
Bundle extras = notification.extras;
|
||||
|
||||
Bundle extras = NotificationCompat.getExtras(notification);
|
||||
|
||||
//dumpExtras(extras);
|
||||
|
||||
@ -321,9 +265,9 @@ public class NotificationListener extends NotificationListenerService {
|
||||
|
||||
CharSequence contentCS = null;
|
||||
if (preferBigText && extras.containsKey(Notification.EXTRA_BIG_TEXT)) {
|
||||
contentCS = extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
|
||||
contentCS = extras.getCharSequence(NotificationCompat.EXTRA_BIG_TEXT);
|
||||
} else if (extras.containsKey(Notification.EXTRA_TEXT)) {
|
||||
contentCS = extras.getCharSequence(Notification.EXTRA_TEXT);
|
||||
contentCS = extras.getCharSequence(NotificationCompat.EXTRA_TEXT);
|
||||
}
|
||||
if (contentCS != null) {
|
||||
notificationSpec.body = contentCS.toString();
|
||||
@ -344,31 +288,18 @@ public class NotificationListener extends NotificationListenerService {
|
||||
/**
|
||||
* Try to handle media session notifications that tell info about the current play state.
|
||||
*
|
||||
* @param notification The notification to handle.
|
||||
* @param mediaSession The mediasession to handle.
|
||||
* @return true if notification was handled, false otherwise
|
||||
*/
|
||||
public boolean handleMediaSessionNotification(Notification notification) {
|
||||
|
||||
// this code requires Android 5.0 or newer
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean handleMediaSessionNotification(MediaSessionCompat.Token mediaSession) {
|
||||
MusicSpec musicSpec = new MusicSpec();
|
||||
MusicStateSpec stateSpec = new MusicStateSpec();
|
||||
|
||||
Bundle extras = notification.extras;
|
||||
if (extras == null)
|
||||
return false;
|
||||
|
||||
if (extras.get(Notification.EXTRA_MEDIA_SESSION) == null)
|
||||
return false;
|
||||
|
||||
MediaController c;
|
||||
MediaControllerCompat c;
|
||||
try {
|
||||
c = new MediaController(getApplicationContext(), (MediaSession.Token) extras.get(Notification.EXTRA_MEDIA_SESSION));
|
||||
c = new MediaControllerCompat(getApplicationContext(), mediaSession);
|
||||
|
||||
PlaybackState s = c.getPlaybackState();
|
||||
PlaybackStateCompat s = c.getPlaybackState();
|
||||
stateSpec.position = (int) (s.getPosition() / 1000);
|
||||
stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed());
|
||||
stateSpec.repeat = 1;
|
||||
@ -388,57 +319,41 @@ public class NotificationListener extends NotificationListenerService {
|
||||
break;
|
||||
}
|
||||
|
||||
MediaMetadata d = c.getMetadata();
|
||||
MediaMetadataCompat d = c.getMetadata();
|
||||
if (d == null)
|
||||
return false;
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_ARTIST))
|
||||
musicSpec.artist = d.getString(MediaMetadata.METADATA_KEY_ARTIST);
|
||||
musicSpec.artist = d.getString(MediaMetadataCompat.METADATA_KEY_ARTIST);
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_ALBUM))
|
||||
musicSpec.album = d.getString(MediaMetadata.METADATA_KEY_ALBUM);
|
||||
musicSpec.album = d.getString(MediaMetadataCompat.METADATA_KEY_ALBUM);
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_TITLE))
|
||||
musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE);
|
||||
musicSpec.track = d.getString(MediaMetadataCompat.METADATA_KEY_TITLE);
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION))
|
||||
musicSpec.duration = (int) d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000;
|
||||
musicSpec.duration = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_DURATION) / 1000;
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS))
|
||||
musicSpec.trackCount = (int) d.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS);
|
||||
musicSpec.trackCount = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS);
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
|
||||
musicSpec.trackNr = (int) d.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
|
||||
musicSpec.trackNr = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER);
|
||||
|
||||
// finally, tell the device about it
|
||||
GBApplication.deviceService().onSetMusicInfo(musicSpec);
|
||||
GBApplication.deviceService().onSetMusicState(stateSpec);
|
||||
|
||||
return true;
|
||||
} catch (NullPointerException e) {
|
||||
} catch (NullPointerException | RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationRemoved(StatusBarNotification sbn) {
|
||||
//FIXME: deduplicate code
|
||||
if (!isServiceRunning() || sbn == null) {
|
||||
if (shouldIgnore(sbn))
|
||||
return;
|
||||
}
|
||||
|
||||
String source = sbn.getPackageName();
|
||||
Notification notification = sbn.getNotification();
|
||||
if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source.equals("android") ||
|
||||
source.equals("com.android.systemui") ||
|
||||
source.equals("com.android.dialer") ||
|
||||
source.equals("com.cyanogenmod.eleven")) {
|
||||
return;
|
||||
}
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (prefs.getBoolean("autoremove_notifications", false)) {
|
||||
LOG.info("notification removed, will ask device to delete it");
|
||||
|
||||
GBApplication.deviceService().onDeleteNotification((int) sbn.getPostTime()); //FIMXE: a truly unique id would be better
|
||||
GBApplication.deviceService().onDeleteNotification(sbn.getPackageName().hashCode() * 31 + sbn.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -451,4 +366,88 @@ public class NotificationListener extends NotificationListenerService {
|
||||
LOG.debug(String.format("Notification extra: %s %s (%s)", key, value.toString(), value.getClass().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldIgnore(StatusBarNotification sbn) {
|
||||
/*
|
||||
* return early if DeviceCommunicationService is not running,
|
||||
* else the service would get started every time we get a notification.
|
||||
* unfortunately we cannot enable/disable NotificationListener at runtime like we do with
|
||||
* broadcast receivers because it seems to invalidate the permissions that are
|
||||
* necessary for NotificationListenerService
|
||||
*/
|
||||
if (!isServiceRunning() || sbn == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (shouldIgnoreSource(sbn.getPackageName()))
|
||||
return true;
|
||||
|
||||
if (shouldIgnoreNotification(sbn.getNotification()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean shouldIgnoreSource(String source) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
|
||||
/* do not display messages from "android"
|
||||
* This includes keyboard selection message, usb connection messages, etc
|
||||
* Hope it does not filter out too much, we will see...
|
||||
*/
|
||||
|
||||
if (source.equals("android") ||
|
||||
source.equals("com.android.systemui") ||
|
||||
source.equals("com.android.dialer") ||
|
||||
source.equals("com.cyanogenmod.eleven")) {
|
||||
LOG.info("Ignoring notification, is a system event");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (source.equals("com.moez.QKSMS") ||
|
||||
source.equals("com.android.mms") ||
|
||||
source.equals("com.sonyericsson.conversations") ||
|
||||
source.equals("com.android.messaging") ||
|
||||
source.equals("org.smssecure.smssecure")) {
|
||||
if (!"never".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (GBApplication.appIsBlacklisted(source)) {
|
||||
LOG.info("Ignoring notification, application is blacklisted");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean shouldIgnoreNotification(Notification notification) {
|
||||
|
||||
MediaSessionCompat.Token mediaSession = NotificationCompat.getMediaSession(notification);
|
||||
//try to handle media session notifications
|
||||
if (mediaSession != null && handleMediaSessionNotification(mediaSession))
|
||||
return true;
|
||||
|
||||
//ignore notifications marked as LocalOnly https://developer.android.com/reference/android/app/Notification.html#FLAG_LOCAL_ONLY
|
||||
if (NotificationCompat.getLocalOnly(notification))
|
||||
return true;
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
|
||||
PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
if (powermanager.isScreenOn()) {
|
||||
// LOG.info("Not forwarding notification, screen seems to be on and settings do not allow this");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) {
|
||||
// LOG.info("Not forwarding notification, FLAG_ONGOING_EVENT is set. Notification flags: " + notification.flags);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.CalendarContract.Instances;
|
||||
import android.text.format.Time;
|
||||
import android.util.Log;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -34,6 +33,8 @@ import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
|
||||
public class CalendarEvents {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CalendarEvents.class);
|
||||
|
||||
@ -102,7 +103,11 @@ public class CalendarEvents {
|
||||
evtCursor.getString(7),
|
||||
!evtCursor.getString(8).equals("0")
|
||||
);
|
||||
if (!GBApplication.calendarIsBlacklisted(calEvent.getCalName())) {
|
||||
calendarEventList.add(calEvent);
|
||||
} else {
|
||||
LOG.debug("calendar " + calEvent.getCalName() + " skipped because it's blacklisted");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -28,15 +28,16 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
* and may not be changed.
|
||||
*/
|
||||
public enum DeviceType {
|
||||
UNKNOWN(-1, R.drawable.ic_launcher, R.drawable.ic_device_default_disabled),
|
||||
UNKNOWN(-1, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled),
|
||||
PEBBLE(1, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled),
|
||||
MIBAND(10, R.drawable.ic_device_miband, R.drawable.ic_device_miband_disabled),
|
||||
MIBAND2(11, R.drawable.ic_device_miband, R.drawable.ic_device_miband_disabled),
|
||||
AMAZFITBIP(12, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
|
||||
VIBRATISSIMO(20, R.drawable.ic_device_lovetoy, R.drawable.ic_device_lovetoy_disabled),
|
||||
LIVEVIEW(30, R.drawable.ic_launcher, R.drawable.ic_device_default_disabled),
|
||||
LIVEVIEW(30, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled),
|
||||
HPLUS(40, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
|
||||
MAKIBESF68(41, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
|
||||
TEST(1000, R.drawable.ic_launcher, R.drawable.ic_device_default_disabled);
|
||||
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled);
|
||||
|
||||
private final int key;
|
||||
@DrawableRes
|
||||
|
@ -18,6 +18,7 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
@ -27,9 +28,11 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -584,6 +587,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
|
||||
if (enable && initialized && coordinator != null && coordinator.supportsCalendarEvents()) {
|
||||
if (mCalendarReceiver == null && getPrefs().getBoolean("enable_calendar_sync", true)) {
|
||||
if (!(GBApplication.isRunningMarshmallowOrLater() && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)) {
|
||||
IntentFilter calendarIntentFilter = new IntentFilter();
|
||||
calendarIntentFilter.addAction("android.intent.action.PROVIDER_CHANGED");
|
||||
calendarIntentFilter.addDataScheme("content");
|
||||
@ -591,6 +595,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
mCalendarReceiver = new CalendarReceiver(mGBDevice);
|
||||
registerReceiver(mCalendarReceiver, calendarIntentFilter);
|
||||
}
|
||||
}
|
||||
if (mAlarmReceiver == null) {
|
||||
mAlarmReceiver = new AlarmReceiver();
|
||||
registerReceiver(mAlarmReceiver, new IntentFilter("DAILY_ALARM"));
|
||||
|
@ -30,6 +30,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.AmazfitBipSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport;
|
||||
@ -109,6 +110,9 @@ public class DeviceSupportFactory {
|
||||
case MIBAND2:
|
||||
deviceSupport = new ServiceDeviceSupport(new MiBand2Support(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case AMAZFITBIP:
|
||||
deviceSupport = new ServiceDeviceSupport(new AmazfitBipSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
case VIBRATISSIMO:
|
||||
deviceSupport = new ServiceDeviceSupport(new VibratissimoSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||
break;
|
||||
|
@ -0,0 +1,202 @@
|
||||
/* Copyright (C) 2016-2017 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.service.btclassic;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.Context;
|
||||
import android.os.ParcelUuid;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public abstract class BtClassicIoThread extends GBDeviceIoThread {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BtClassicIoThread.class);
|
||||
|
||||
private final GBDeviceProtocol mProtocol;
|
||||
private final AbstractSerialDeviceSupport mDeviceSupport;
|
||||
|
||||
|
||||
private BluetoothAdapter mBtAdapter = null;
|
||||
private BluetoothSocket mBtSocket = null;
|
||||
private InputStream mInStream = null;
|
||||
private OutputStream mOutStream = null;
|
||||
private boolean mQuit = false;
|
||||
|
||||
@Override
|
||||
public void quit() {
|
||||
mQuit = true;
|
||||
if (mBtSocket != null) {
|
||||
try {
|
||||
mBtSocket.close();
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean mIsConnected = false;
|
||||
|
||||
|
||||
public BtClassicIoThread(GBDevice gbDevice, Context context, GBDeviceProtocol deviceProtocol, AbstractSerialDeviceSupport deviceSupport, BluetoothAdapter btAdapter) {
|
||||
super(gbDevice, context);
|
||||
mProtocol = deviceProtocol;
|
||||
mDeviceSupport = deviceSupport;
|
||||
mBtAdapter = btAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] bytes) {
|
||||
if (null == bytes)
|
||||
return;
|
||||
LOG.debug("writing:" + GB.hexdump(bytes, 0, bytes.length));
|
||||
try {
|
||||
mOutStream.write(bytes);
|
||||
mOutStream.flush();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error writing.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mIsConnected = connect();
|
||||
if (!mIsConnected) {
|
||||
setUpdateState(GBDevice.State.NOT_CONNECTED);
|
||||
return;
|
||||
}
|
||||
mQuit = false;
|
||||
|
||||
while (!mQuit) {
|
||||
LOG.info("Ready for a new message exchange.");
|
||||
|
||||
try {
|
||||
GBDeviceEvent deviceEvents[] = mProtocol.decodeResponse(parseIncoming(mInStream));
|
||||
if (deviceEvents == null) {
|
||||
LOG.info("unhandled message");
|
||||
} else {
|
||||
for (GBDeviceEvent deviceEvent : deviceEvents) {
|
||||
if (deviceEvent == null) {
|
||||
continue;
|
||||
}
|
||||
mDeviceSupport.evaluateGBDeviceEvent(deviceEvent);
|
||||
}
|
||||
}
|
||||
} catch (SocketTimeoutException ignore) {
|
||||
LOG.debug("socket timeout, we can't help but ignore this");
|
||||
} catch (IOException e) {
|
||||
LOG.info(e.getMessage());
|
||||
mIsConnected = false;
|
||||
mBtSocket = null;
|
||||
mInStream = null;
|
||||
mOutStream = null;
|
||||
LOG.info("Bluetooth socket closed, will quit IO Thread");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mIsConnected = false;
|
||||
if (mBtSocket != null) {
|
||||
try {
|
||||
mBtSocket.close();
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
mBtSocket = null;
|
||||
}
|
||||
setUpdateState(GBDevice.State.NOT_CONNECTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean connect() {
|
||||
GBDevice.State originalState = gbDevice.getState();
|
||||
setUpdateState(GBDevice.State.CONNECTING);
|
||||
|
||||
try {
|
||||
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(gbDevice.getAddress());
|
||||
ParcelUuid uuids[] = btDevice.getUuids();
|
||||
if (uuids == null) {
|
||||
LOG.warn("Device provided no UUIDs to connect to, giving up: " + gbDevice);
|
||||
return false;
|
||||
}
|
||||
for (ParcelUuid uuid : uuids) {
|
||||
LOG.info("found service UUID " + uuid);
|
||||
}
|
||||
mBtSocket = btDevice.createRfcommSocketToServiceRecord(getUuidToConnect(uuids));
|
||||
mBtSocket.connect();
|
||||
mInStream = mBtSocket.getInputStream();
|
||||
mOutStream = mBtSocket.getOutputStream();
|
||||
setUpdateState(GBDevice.State.CONNECTED);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Server socket cannot be started.");
|
||||
//LOG.error(e.getMessage());
|
||||
setUpdateState(originalState);
|
||||
mInStream = null;
|
||||
mOutStream = null;
|
||||
mBtSocket = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
write(mProtocol.encodeSetTime());
|
||||
setUpdateState(GBDevice.State.INITIALIZED);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the uuid to connect to.
|
||||
* Default implementation returns the first of the given uuids that were
|
||||
* read from the remote device.
|
||||
* @param uuids
|
||||
* @return
|
||||
*/
|
||||
@NonNull
|
||||
protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) {
|
||||
return uuids[0].getUuid();
|
||||
}
|
||||
|
||||
protected void setUpdateState(GBDevice.State state) {
|
||||
gbDevice.setState(state);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an incoming message for consuming by the GBDeviceProtocol
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @param inStream
|
||||
*/
|
||||
protected abstract byte[] parseIncoming(InputStream inStream) throws IOException;
|
||||
}
|
@ -1,3 +1,19 @@
|
||||
/* Copyright (C) 2015-2017 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/>. */
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer, Uwe Hermann
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -37,7 +37,8 @@ public enum AlertCategory {
|
||||
// 10-250 reserved for future use
|
||||
// 251-255 defined by service specification
|
||||
Any(255),
|
||||
Custom(-1);
|
||||
Custom(-1),
|
||||
CustomMiBand2(-6);
|
||||
|
||||
private final int id;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -33,12 +33,16 @@ import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public class AlertNotificationProfile<T extends AbstractBTLEDeviceSupport> extends AbstractBleProfile<T> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AlertNotificationProfile.class);
|
||||
private static final int MAX_MSG_LENGTH = 18;
|
||||
private int maxLength = 18; // Mi2-ism?
|
||||
|
||||
public AlertNotificationProfile(T support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
public void setMaxLength(int maxLength) {
|
||||
this.maxLength = maxLength;
|
||||
}
|
||||
|
||||
public void configure(TransactionBuilder builder, AlertNotificationControl control) {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_NOTIFICATION_CONTROL_POINT);
|
||||
if (characteristic != null) {
|
||||
@ -57,21 +61,21 @@ public class AlertNotificationProfile<T extends AbstractBTLEDeviceSupport> exten
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_NEW_ALERT);
|
||||
if (characteristic != null) {
|
||||
String message = StringUtils.ensureNotNull(alert.getMessage());
|
||||
if (message.length() > MAX_MSG_LENGTH && strategy == OverflowStrategy.TRUNCATE) {
|
||||
message = StringUtils.truncate(message, MAX_MSG_LENGTH);
|
||||
if (message.length() > maxLength && strategy == OverflowStrategy.TRUNCATE) {
|
||||
message = StringUtils.truncate(message, maxLength);
|
||||
}
|
||||
|
||||
int numChunks = message.length() / MAX_MSG_LENGTH;
|
||||
if (message.length() % MAX_MSG_LENGTH > 0) {
|
||||
int numChunks = message.length() / maxLength;
|
||||
if (message.length() % maxLength > 0) {
|
||||
numChunks++;
|
||||
}
|
||||
|
||||
try {
|
||||
boolean hasAlerted = false;
|
||||
for (int i = 0; i < numChunks; i++) {
|
||||
int offset = i * MAX_MSG_LENGTH;
|
||||
int offset = i * maxLength;
|
||||
int restLength = message.length() - offset;
|
||||
message = message.substring(offset, offset + Math.min(MAX_MSG_LENGTH, restLength));
|
||||
message = message.substring(offset, offset + Math.min(maxLength, restLength));
|
||||
if (hasAlerted && message.length() == 0) {
|
||||
// no need to do it again when there is no text content
|
||||
break;
|
||||
@ -91,10 +95,17 @@ public class AlertNotificationProfile<T extends AbstractBTLEDeviceSupport> exten
|
||||
}
|
||||
}
|
||||
|
||||
public void newAlert(TransactionBuilder builder, NewAlert alert) {
|
||||
newAlert(builder, alert, OverflowStrategy.TRUNCATE);
|
||||
}
|
||||
|
||||
protected byte[] getAlertMessage(NewAlert alert, String message, int chunk) throws IOException {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream(100);
|
||||
stream.write(BLETypeConversions.fromUint8(alert.getCategory().getId()));
|
||||
stream.write(BLETypeConversions.fromUint8(alert.getNumAlerts()));
|
||||
if (alert.getCategory() == AlertCategory.CustomMiBand2) {
|
||||
stream.write(BLETypeConversions.fromUint8(alert.getCustomIcon()));
|
||||
}
|
||||
|
||||
if (message.length() > 0) {
|
||||
stream.write(BLETypeConversions.toUtf8s(message));
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification;
|
||||
|
||||
import android.icu.util.IslamicCalendar;
|
||||
|
||||
/**
|
||||
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.new_alert.xml&u=org.bluetooth.characteristic.new_alert.xml
|
||||
*
|
||||
@ -47,6 +49,7 @@ public class NewAlert {
|
||||
private final AlertCategory category;
|
||||
private final int numAlerts;
|
||||
private final String message;
|
||||
private int customIcon = -1;
|
||||
|
||||
public NewAlert(AlertCategory category, int /*uint8*/ numAlerts, String /*utf8s*/ message) {
|
||||
this.category = category;
|
||||
@ -54,6 +57,13 @@ public class NewAlert {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public NewAlert(AlertCategory category, int /*uint8*/ numAlerts, String /*utf8s*/ message, int customIcon) {
|
||||
this.category = category;
|
||||
this.numAlerts = numAlerts;
|
||||
this.message = message;
|
||||
this.customIcon = customIcon;
|
||||
}
|
||||
|
||||
public AlertCategory getCategory() {
|
||||
return category;
|
||||
}
|
||||
@ -65,4 +75,8 @@ public class NewAlert {
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public int getCustomIcon() {
|
||||
return customIcon;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,80 @@
|
||||
/* Copyright (C) 2017 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.amazfitbip;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.FirmwareType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.Mi2FirmwareInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
||||
|
||||
public class AmazfitBipFirmwareInfo extends Mi2FirmwareInfo {
|
||||
// total crap maybe
|
||||
private static final byte[] GPS_HEADER = new byte[]{
|
||||
(byte) 0xcb, 0x51, (byte) 0xc1, 0x30, 0x41, (byte) 0x9e, 0x5e, (byte) 0xd3,
|
||||
0x51, 0x35, (byte) 0xdf, 0x66, (byte) 0xed, (byte) 0xd9, 0x5f, (byte) 0xa7
|
||||
};
|
||||
|
||||
// guessed - at least it is the same accross current versions and different from other devices
|
||||
private static final byte[] FW_HEADER = new byte[]{
|
||||
0x68, 0x46, 0x70, 0x47, 0x68, 0x46, 0x70, 0x47,
|
||||
0x68, 0x46, 0x70, 0x47, 0x68, 0x46, 0x70, 0x47
|
||||
};
|
||||
|
||||
private static final int FW_HEADER_OFFSET = 0x9330;
|
||||
|
||||
private static final byte[] RES_HEADER = new byte[]{ // HMRES resources file (*.res)
|
||||
0x48, 0x4d, 0x52, 0x45, 0x53
|
||||
};
|
||||
|
||||
|
||||
static {
|
||||
// firmware
|
||||
crcToVersion.put(25257, "0.0.8.74");
|
||||
|
||||
// resources
|
||||
crcToVersion.put(12586, "RES 0.0.8.74");
|
||||
|
||||
// gps
|
||||
crcToVersion.put(61520, "GPS 0.0.8.xx");
|
||||
}
|
||||
|
||||
public AmazfitBipFirmwareInfo(byte[] bytes) {
|
||||
super(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FirmwareType determineFirmwareType(byte[] bytes) {
|
||||
if (ArrayUtils.startsWith(bytes, RES_HEADER)) {
|
||||
return FirmwareType.RES;
|
||||
}
|
||||
if (ArrayUtils.startsWith(bytes, GPS_HEADER)) {
|
||||
return FirmwareType.GPS;
|
||||
}
|
||||
if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET)) {
|
||||
// TODO: this is certainly not a correct validation, but it works for now
|
||||
return FirmwareType.FIRMWARE;
|
||||
}
|
||||
return FirmwareType.INVALID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGenerallyCompatibleWith(GBDevice device) {
|
||||
return isHeaderValid() && device.getType() == DeviceType.AMAZFITBIP;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
/* Copyright (C) 2017 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.amazfitbip;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.SimpleTimeZone;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipIcon;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipWeatherConditions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
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.NewAlert;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.operations.AmazfitBipUpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
||||
|
||||
public class AmazfitBipSupport extends MiBand2Support {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipSupport.class);
|
||||
|
||||
@Override
|
||||
public NotificationStrategy getNotificationStrategy() {
|
||||
return new AmazfitBipTextNotificationStrategy(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotification(NotificationSpec notificationSpec) {
|
||||
if (notificationSpec.type == NotificationType.GENERIC_ALARM_CLOCK) {
|
||||
onAlarmClock(notificationSpec);
|
||||
return;
|
||||
}
|
||||
|
||||
String senderOrTiltle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
|
||||
|
||||
String message = StringUtils.truncate(senderOrTiltle, 32) + "\0";
|
||||
if (notificationSpec.subject != null) {
|
||||
message += StringUtils.truncate(notificationSpec.subject, 128) + "\n\n";
|
||||
}
|
||||
if (notificationSpec.body != null) {
|
||||
message += StringUtils.truncate(notificationSpec.body, 128);
|
||||
}
|
||||
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("new notification");
|
||||
AlertNotificationProfile<?> profile = new AlertNotificationProfile(this);
|
||||
profile.setMaxLength(255); // TODO: find out real limit, certainly it is more than 18 which is default
|
||||
|
||||
int customIconId = AmazfitBipIcon.mapToIconId(notificationSpec.type);
|
||||
|
||||
AlertCategory alertCategory = AlertCategory.CustomMiBand2;
|
||||
|
||||
// The SMS icon for AlertCategory.SMS is unique and not available as iconId
|
||||
if (notificationSpec.type == NotificationType.GENERIC_SMS) {
|
||||
alertCategory = AlertCategory.SMS;
|
||||
}
|
||||
|
||||
NewAlert alert = new NewAlert(alertCategory, 1, message, customIconId);
|
||||
profile.newAlert(builder, alert);
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to send notification to Amazfit Bip", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean start) {
|
||||
CallSpec callSpec = new CallSpec();
|
||||
callSpec.command = start ? CallSpec.CALL_INCOMING : CallSpec.CALL_END;
|
||||
callSpec.name = "Gadgetbridge";
|
||||
onSetCallState(callSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleButtonPressed(byte[] value) {
|
||||
if (value == null || value.length != 1) {
|
||||
return;
|
||||
}
|
||||
GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
|
||||
|
||||
if (value[0] == 0x07) {
|
||||
callCmd.event = GBDeviceEventCallControl.Event.REJECT;
|
||||
} else if (value[0] == 0x09) {
|
||||
callCmd.event = GBDeviceEventCallControl.Event.ACCEPT;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
evaluateGBDeviceEvent(callCmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInstallApp(Uri uri) {
|
||||
try {
|
||||
new AmazfitBipUpdateFirmwareOperation(uri, this).perform();
|
||||
} catch (IOException ex) {
|
||||
GB.toast(getContext(), "Firmware cannot be installed: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("Sending weather forecast");
|
||||
Version version = new Version(gbDevice.getFirmwareVersion());
|
||||
|
||||
boolean supportsConditionString = false;
|
||||
if (version.compareTo(new Version("0.0.8.74")) >= 0) {
|
||||
supportsConditionString = true;
|
||||
}
|
||||
|
||||
final byte NR_DAYS = 2;
|
||||
int bytesPerDay = 4;
|
||||
int conditionsLength = 0;
|
||||
if (supportsConditionString) {
|
||||
bytesPerDay = 5;
|
||||
conditionsLength = weatherSpec.currentCondition.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);
|
||||
int tz_offset_hours = SimpleTimeZone.getDefault().getOffset(weatherSpec.timestamp * 1000L) / (1000 * 60 * 60);
|
||||
buf.put((byte) (tz_offset_hours * 4));
|
||||
|
||||
buf.put(NR_DAYS);
|
||||
|
||||
byte condition = AmazfitBipWeatherConditions.mapToAmazfitBipWeatherCode(weatherSpec.currentConditionCode);
|
||||
buf.put(condition);
|
||||
buf.put(condition);
|
||||
buf.put((byte) (weatherSpec.todayMaxTemp - 273));
|
||||
buf.put((byte) (weatherSpec.todayMinTemp - 273));
|
||||
if (supportsConditionString) {
|
||||
buf.put(weatherSpec.currentCondition.getBytes());
|
||||
buf.put((byte) 0); //
|
||||
}
|
||||
condition = AmazfitBipWeatherConditions.mapToAmazfitBipWeatherCode(weatherSpec.tomorrowConditionCode);
|
||||
|
||||
buf.put(condition);
|
||||
buf.put(condition);
|
||||
buf.put((byte) (weatherSpec.tomorrowMaxTemp - 273));
|
||||
buf.put((byte) (weatherSpec.tomorrowMinTemp - 273));
|
||||
if (supportsConditionString) {
|
||||
buf.put((byte) 0); // not yet in weatherspec
|
||||
}
|
||||
|
||||
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/* Copyright (C) 2017 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.amazfitbip;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
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.NewAlert;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.OverflowStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.Mi2TextNotificationStrategy;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
|
||||
// This class in no longer in use except for incoming calls
|
||||
class AmazfitBipTextNotificationStrategy extends Mi2TextNotificationStrategy {
|
||||
|
||||
AmazfitBipTextNotificationStrategy(MiBand2Support support) {
|
||||
super(support);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, BtLEAction extraAction, TransactionBuilder builder) {
|
||||
if (simpleNotification != null && !StringUtils.isEmpty(simpleNotification.getMessage())) {
|
||||
sendAlert(simpleNotification, builder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendAlert(@NonNull SimpleNotification simpleNotification, TransactionBuilder builder) {
|
||||
AlertNotificationProfile<?> profile = new AlertNotificationProfile<>(getSupport());
|
||||
profile.setMaxLength(255); // TODO: find out real limit, certainly it is more than 18 which is default
|
||||
|
||||
AlertCategory category = simpleNotification.getAlertCategory();
|
||||
switch (simpleNotification.getAlertCategory()) {
|
||||
// only these are confirmed working so far on Amazfit Bip
|
||||
case Email:
|
||||
case IncomingCall:
|
||||
case SMS:
|
||||
break;
|
||||
// default to SMS for non working categories
|
||||
default:
|
||||
category = AlertCategory.SMS;
|
||||
}
|
||||
NewAlert alert = new NewAlert(category, 1, simpleNotification.getMessage());
|
||||
profile.newAlert(builder, alert, OverflowStrategy.TRUNCATE);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/* Copyright (C) 2017 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.amazfitbip.operations;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.AmazfitBipSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class AmazfitBipUpdateFirmwareOperation extends UpdateFirmwareOperation {
|
||||
public AmazfitBipUpdateFirmwareOperation(Uri uri, AmazfitBipSupport support) {
|
||||
super(uri, support);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPerform() throws IOException {
|
||||
AmazfitBipFWHelper mFwHelper = new AmazfitBipFWHelper(uri, getContext());
|
||||
|
||||
firmwareInfo = mFwHelper.getFirmwareInfo();
|
||||
if (!firmwareInfo.isGenerallyCompatibleWith(getDevice())) {
|
||||
throw new IOException("Firmware is not compatible with the given device: " + getDevice().getAddress());
|
||||
}
|
||||
|
||||
if (!sendFwInfo()) {
|
||||
displayMessage(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR);
|
||||
done();
|
||||
}
|
||||
//the firmware will be sent by the notification listener if the band confirms that the metadata are ok.
|
||||
}
|
||||
}
|
@ -39,23 +39,23 @@ public class HPlusDataRecordDaySlot extends HPlusDataRecord {
|
||||
/**
|
||||
* Number of steps
|
||||
*/
|
||||
public int steps;
|
||||
public int steps = ActivitySample.NOT_MEASURED;
|
||||
|
||||
/**
|
||||
* Number of seconds without activity (TBC)
|
||||
*/
|
||||
public int secondsInactive;
|
||||
public int secondsInactive = ActivitySample.NOT_MEASURED;
|
||||
|
||||
/**
|
||||
* Average Heart Rate in Beats Per Minute
|
||||
*/
|
||||
public int heartRate;
|
||||
public int heartRate = ActivitySample.NOT_MEASURED;
|
||||
|
||||
private int age = 0;
|
||||
/**
|
||||
* Raw intensity calculated from calories
|
||||
*/
|
||||
public int intensity;
|
||||
public int intensity = ActivitySample.NOT_MEASURED;
|
||||
|
||||
public HPlusDataRecordDaySlot(byte[] data, int age) {
|
||||
super(data, TYPE_DAY_SLOT);
|
||||
@ -85,6 +85,8 @@ public class HPlusDataRecordDaySlot extends HPlusDataRecord {
|
||||
timestamp = (int) (slotTime.getTimeInMillis() / 1000L);
|
||||
|
||||
this.age = age;
|
||||
|
||||
intensity = (int) ((100*heartRate)/(208-0.7*age));
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
|
@ -24,6 +24,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
@ -80,19 +81,24 @@ class HPlusDataRecordRealtime extends HPlusDataRecord {
|
||||
int y = (data[8] & 0xFF) * 256 + (data[7] & 0xFF);
|
||||
|
||||
battery = data[9];
|
||||
|
||||
calories = x + y; // KCal
|
||||
|
||||
heartRate = data[11] & 0xFF; // BPM
|
||||
activeTime = (data[14] & 0xFF * 256) + (data[13] & 0xFF);
|
||||
|
||||
if (battery == 255) {
|
||||
battery = ActivitySample.NOT_MEASURED;
|
||||
heartRate = ActivitySample.NOT_MEASURED;
|
||||
intensity = 0;
|
||||
activityKind = ActivityKind.TYPE_NOT_WORN;
|
||||
} else {
|
||||
heartRate = data[11] & 0xFF; // BPM
|
||||
if (heartRate == 255) {
|
||||
intensity = 0;
|
||||
activityKind = ActivityKind.TYPE_NOT_MEASURED;
|
||||
heartRate = ActivitySample.NOT_MEASURED;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
intensity = (int) ((100 * heartRate) / (208 - 0.7 * age));
|
||||
activityKind = ActivityKind.TYPE_UNKNOWN;
|
||||
activityKind = HPlusDataRecord.TYPE_REALTIME;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusHealthSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HPlusHealthActivityOverlay;
|
||||
@ -247,7 +248,6 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
}
|
||||
|
||||
if (mSlotsInitialSync) {
|
||||
|
||||
//If the slot is in the future, actually it is from the previous day
|
||||
//Subtract a day of seconds
|
||||
if (record.slot > nowSlot) {
|
||||
@ -286,6 +286,8 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
}
|
||||
});
|
||||
|
||||
List<Integer> notWornSlots = new ArrayList<>();
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
List<HPlusHealthActivitySample> samples = new ArrayList<>();
|
||||
@ -300,16 +302,55 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
|
||||
sample.setRawHPlusHealthData(storedRecord.getRawData());
|
||||
sample.setSteps(storedRecord.steps);
|
||||
|
||||
sample.setRawIntensity(storedRecord.intensity);
|
||||
sample.setHeartRate(storedRecord.heartRate);
|
||||
sample.setRawKind(storedRecord.type);
|
||||
sample.setRawIntensity(record.intensity);
|
||||
sample.setProvider(provider);
|
||||
samples.add(sample);
|
||||
|
||||
if (HPlusCoordinator.getAllDayHR(gbDevice.getAddress()) == HPlusConstants.ARG_HEARTRATE_ALLDAY_ON && storedRecord.heartRate == ActivitySample.NOT_MEASURED && storedRecord.steps <= 0) {
|
||||
notWornSlots.add(sample.getTimestamp());
|
||||
notWornSlots.add(sample.getTimestamp() + 10 * 60);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
provider.getSampleDao().insertOrReplaceInTx(samples);
|
||||
mDaySlotRecords.clear();
|
||||
|
||||
//Create an overlay with unused slots
|
||||
if (notWornSlots.size() > 0) {
|
||||
DaoSession session = dbHandler.getDaoSession();
|
||||
Long userId = DBHelper.getUser(session).getId();
|
||||
Long deviceId = DBHelper.getDevice(getDevice(), session).getId();
|
||||
|
||||
HPlusHealthActivityOverlayDao overlayDao = session.getHPlusHealthActivityOverlayDao();
|
||||
List<HPlusHealthActivityOverlay> overlayList = new ArrayList<>();
|
||||
|
||||
|
||||
int firstSlotTimestamp = notWornSlots.get(0);
|
||||
int lastSlotTimestamp = notWornSlots.get(0);
|
||||
|
||||
int i = 1;
|
||||
for (Integer timestamp : notWornSlots) {
|
||||
|
||||
//If it is the last of the samples or of the interruption period
|
||||
if (timestamp - lastSlotTimestamp > 10 * 60) {
|
||||
overlayList.add(new HPlusHealthActivityOverlay(firstSlotTimestamp, lastSlotTimestamp, ActivityKind.TYPE_NOT_WORN, deviceId, userId, null));
|
||||
firstSlotTimestamp = timestamp;
|
||||
}
|
||||
|
||||
lastSlotTimestamp = timestamp;
|
||||
|
||||
}
|
||||
|
||||
if (firstSlotTimestamp != lastSlotTimestamp)
|
||||
overlayList.add(new HPlusHealthActivityOverlay(firstSlotTimestamp, lastSlotTimestamp, ActivityKind.TYPE_NOT_WORN, deviceId, userId, null));
|
||||
|
||||
overlayDao.insertOrReplaceInTx(overlayList);
|
||||
}
|
||||
|
||||
} catch (GBException ex) {
|
||||
LOG.info((ex.getMessage()));
|
||||
} catch (Exception ex) {
|
||||
@ -402,17 +443,6 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
|
||||
getDevice().setBatteryLevel(record.battery);
|
||||
|
||||
//Skip when measuring heart rate
|
||||
//Calories and Distance are updated and these values will be lost.
|
||||
//Because a message with a valid Heart Rate will be provided, this loss very limited
|
||||
if(record.heartRate == ActivityKind.TYPE_NOT_MEASURED) {
|
||||
getDevice().setFirmwareVersion2("---");
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}else {
|
||||
getDevice().setFirmwareVersion2("" + record.heartRate);
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
HPlusHealthSampleProvider provider = new HPlusHealthSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||
|
||||
@ -506,7 +536,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
int hwMinor = data[1] & 0xFF;
|
||||
|
||||
getDevice().setFirmwareVersion2(hwMajor + "." + hwMinor);
|
||||
mHPlusSupport.setUnicodeSupport((data[3] != 0));
|
||||
mHPlusSupport.setUnicodeSupport(data[3] != 0);
|
||||
} else {
|
||||
major = data[2] & 0xFF;
|
||||
minor = data[1] & 0xFF;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2016-2017 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
ivanovlev, João Paulo Barraca
|
||||
ivanovlev, João Paulo Barraca, Pavel Motyrev
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -808,6 +808,8 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
private byte[] encodeStringToDevice(String s) {
|
||||
|
||||
List<Byte> outBytes = new ArrayList<Byte>();
|
||||
Boolean unicode = HPlusCoordinator.getUnicodeSupport(this.gbDevice.getAddress());
|
||||
LOG.info("Encode String: Unicode=" + unicode);
|
||||
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
Character c = s.charAt(i);
|
||||
@ -817,13 +819,14 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
cs = HPlusConstants.transliterateMap.get(c);
|
||||
} else {
|
||||
try {
|
||||
if(HPlusCoordinator.getUnicodeSupport(this.gbDevice.getAddress()))
|
||||
if(unicode)
|
||||
cs = c.toString().getBytes("Unicode");
|
||||
else
|
||||
cs = c.toString().getBytes("GB2312");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
//Fallback. Result string may be strange, but better than nothing
|
||||
cs = c.toString().getBytes();
|
||||
LOG.error("Could not convert String to Bytes: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < cs.length; j++)
|
||||
@ -884,7 +887,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
String DEVINFO_STEP = getContext().getString(R.string.chart_steps) + ": ";
|
||||
String DEVINFO_DISTANCE = getContext().getString(R.string.distance) + ": ";
|
||||
String DEVINFO_CALORY = getContext().getString(R.string.calories) + ": ";
|
||||
String DEVINFO_HEART = getContext().getString(R.string.charts_legend_heartrate);
|
||||
String DEVINFO_HEART = getContext().getString(R.string.charts_legend_heartrate) + ": ";
|
||||
|
||||
String info = "";
|
||||
if (record.steps > 0) {
|
||||
|
@ -17,10 +17,7 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.liveview;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.Context;
|
||||
import android.os.ParcelUuid;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -28,158 +25,25 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class LiveviewIoThread extends GBDeviceIoThread {
|
||||
public class LiveviewIoThread extends BtClassicIoThread {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LiveviewIoThread.class);
|
||||
|
||||
private static final UUID SERIAL = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
|
||||
|
||||
private final LiveviewProtocol mLiveviewProtocol;
|
||||
private final LiveviewSupport mLiveviewSupport;
|
||||
|
||||
|
||||
private BluetoothAdapter mBtAdapter = null;
|
||||
private BluetoothSocket mBtSocket = null;
|
||||
private InputStream mInStream = null;
|
||||
private OutputStream mOutStream = null;
|
||||
private boolean mQuit = false;
|
||||
|
||||
@Override
|
||||
public void quit() {
|
||||
mQuit = true;
|
||||
if (mBtSocket != null) {
|
||||
try {
|
||||
mBtSocket.close();
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean mIsConnected = false;
|
||||
|
||||
|
||||
public LiveviewIoThread(GBDevice gbDevice, Context context, GBDeviceProtocol lvProtocol, LiveviewSupport lvSupport, BluetoothAdapter lvBtAdapter) {
|
||||
super(gbDevice, context);
|
||||
mLiveviewProtocol = (LiveviewProtocol) lvProtocol;
|
||||
mBtAdapter = lvBtAdapter;
|
||||
mLiveviewSupport = lvSupport;
|
||||
super(gbDevice, context, lvProtocol, lvSupport, lvBtAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] bytes) {
|
||||
if (null == bytes)
|
||||
return;
|
||||
LOG.debug("writing:" + GB.hexdump(bytes, 0, bytes.length));
|
||||
try {
|
||||
mOutStream.write(bytes);
|
||||
mOutStream.flush();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error writing.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mIsConnected = connect();
|
||||
if (!mIsConnected) {
|
||||
setUpdateState(GBDevice.State.NOT_CONNECTED);
|
||||
return;
|
||||
}
|
||||
mQuit = false;
|
||||
|
||||
while (!mQuit) {
|
||||
LOG.info("Ready for a new message exchange.");
|
||||
|
||||
try {
|
||||
GBDeviceEvent deviceEvents[] = mLiveviewProtocol.decodeResponse(parseIncoming());
|
||||
if (deviceEvents == null) {
|
||||
LOG.info("unhandled message");
|
||||
} else {
|
||||
for (GBDeviceEvent deviceEvent : deviceEvents) {
|
||||
if (deviceEvent == null) {
|
||||
continue;
|
||||
}
|
||||
mLiveviewSupport.evaluateGBDeviceEvent(deviceEvent);
|
||||
}
|
||||
}
|
||||
} catch (SocketTimeoutException ignore) {
|
||||
LOG.debug("socket timeout, we can't help but ignore this");
|
||||
} catch (IOException e) {
|
||||
LOG.info(e.getMessage());
|
||||
mIsConnected = false;
|
||||
mBtSocket = null;
|
||||
mInStream = null;
|
||||
mOutStream = null;
|
||||
LOG.info("Bluetooth socket closed, will quit IO Thread");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mIsConnected = false;
|
||||
if (mBtSocket != null) {
|
||||
try {
|
||||
mBtSocket.close();
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
mBtSocket = null;
|
||||
}
|
||||
setUpdateState(GBDevice.State.NOT_CONNECTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean connect() {
|
||||
GBDevice.State originalState = gbDevice.getState();
|
||||
setUpdateState(GBDevice.State.CONNECTING);
|
||||
|
||||
try {
|
||||
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(gbDevice.getAddress());
|
||||
ParcelUuid uuids[] = btDevice.getUuids();
|
||||
if (uuids == null) {
|
||||
return false;
|
||||
}
|
||||
for (ParcelUuid uuid : uuids) {
|
||||
LOG.info("found service UUID " + uuid);
|
||||
}
|
||||
mBtSocket = btDevice.createRfcommSocketToServiceRecord(uuids[0].getUuid());
|
||||
mBtSocket.connect();
|
||||
mInStream = mBtSocket.getInputStream();
|
||||
mOutStream = mBtSocket.getOutputStream();
|
||||
setUpdateState(GBDevice.State.CONNECTED);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Server socket cannot be started.");
|
||||
//LOG.error(e.getMessage());
|
||||
setUpdateState(originalState);
|
||||
mInStream = null;
|
||||
mOutStream = null;
|
||||
mBtSocket = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
write(mLiveviewProtocol.encodeSetTime());
|
||||
setUpdateState(GBDevice.State.INITIALIZED);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setUpdateState(GBDevice.State state) {
|
||||
gbDevice.setState(state);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
private byte[] parseIncoming() throws IOException {
|
||||
protected byte[] parseIncoming(InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream msgStream = new ByteArrayOutputStream();
|
||||
|
||||
boolean finished = false;
|
||||
@ -187,7 +51,7 @@ public class LiveviewIoThread extends GBDeviceIoThread {
|
||||
byte[] incoming = new byte[1];
|
||||
|
||||
while (!finished) {
|
||||
mInStream.read(incoming);
|
||||
inputStream.read(incoming);
|
||||
msgStream.write(incoming);
|
||||
|
||||
switch (state) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, atkyritsis, Carsten Pfeiffer,
|
||||
Christian Fischer, Daniele Gobbetti, JohnnySun, Julien Pivotto, Kasha,
|
||||
Sergey Trofimov, Steffen Liebergeld
|
||||
Christian Fischer, Daniele Gobbetti, freezed-or-frozen, JohnnySun, Julien
|
||||
Pivotto, Kasha, Sergey Trofimov, Steffen Liebergeld
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -1270,8 +1270,26 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
}
|
||||
|
||||
private void handleSensorData(byte[] value) {
|
||||
int counter=0, step=0, axis1=0, axis2=0, axis3 =0;
|
||||
/**
|
||||
* Analyse and decode sensor data from ADXL362 accelerometer
|
||||
* @param value to decode
|
||||
* @return nothing
|
||||
*
|
||||
* Each axis raw value is 16bits long and look like : ttssvvvvvvvvvvvv
|
||||
* tt : 2 bits for the type of data (00=x, 01=y, 10=z, 11=temperature)
|
||||
* ss : sign of the value
|
||||
* vvvvvvvvvvvv : accelerometer value encoded using two complements
|
||||
*
|
||||
* TODO: Because each accelerometer is different, all values should be calibrated with :
|
||||
* a scale factor
|
||||
* an offset factor
|
||||
*/
|
||||
private static void handleSensorData(byte[] value) {
|
||||
int counter=0, step=0;
|
||||
double xAxis=0.0, yAxis=0.0, zAxis=0.0;
|
||||
double scale_factor = 1000.0;
|
||||
double gravity = 9.81;
|
||||
|
||||
if ((value.length - 2) % 6 != 0) {
|
||||
LOG.warn("GOT UNEXPECTED SENSOR DATA WITH LENGTH: " + value.length);
|
||||
for (byte b : value) {
|
||||
@ -1282,11 +1300,46 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
counter = (value[0] & 0xff) | ((value[1] & 0xff) << 8);
|
||||
for (int idx = 0; idx < ((value.length - 2) / 6); idx++) {
|
||||
step = idx * 6;
|
||||
axis1 = (value[step+2] & 0xff) | ((value[step+3] & 0xff) << 8);
|
||||
axis2 = (value[step+4] & 0xff) | ((value[step+5] & 0xff) << 8);
|
||||
axis3 = (value[step+6] & 0xff) | ((value[step+7] & 0xff) << 8);
|
||||
|
||||
// Analyse X-axis data
|
||||
int xAxisRawValue = (value[step+2] & 0xff) | ((value[step+3] & 0xff) << 8);
|
||||
int xAxisSign = (value[step+3] & 0x30) >> 4;
|
||||
int xAxisType = (value[step+3] & 0xc0) >> 6;
|
||||
if (xAxisSign == 0) {
|
||||
xAxis = xAxisRawValue & 0xfff;
|
||||
}
|
||||
else {
|
||||
xAxis = (xAxisRawValue & 0xfff) - 4097;
|
||||
}
|
||||
xAxis = (xAxis*1.0 / scale_factor) * gravity;
|
||||
|
||||
// Analyse Y-axis data
|
||||
int yAxisRawValue = (value[step+4] & 0xff) | ((value[step+5] & 0xff) << 8);
|
||||
int yAxisSign = (value[step+5] & 0x30) >> 4;
|
||||
int yAxisType = (value[step+5] & 0xc0) >> 6;
|
||||
if (yAxisSign == 0) {
|
||||
yAxis = yAxisRawValue & 0xfff;
|
||||
}
|
||||
else {
|
||||
yAxis = (yAxisRawValue & 0xfff) - 4097;
|
||||
}
|
||||
yAxis = (yAxis / scale_factor) * gravity;
|
||||
|
||||
// Analyse Z-axis data
|
||||
int zAxisRawValue = (value[step+6] & 0xff) | ((value[step+7] & 0xff) << 8);
|
||||
int zAxisSign = (value[step+7] & 0x30) >> 4;
|
||||
int zAxisType = (value[step+7] & 0xc0) >> 6;
|
||||
if (zAxisSign == 0) {
|
||||
zAxis = zAxisRawValue & 0xfff;
|
||||
}
|
||||
else {
|
||||
zAxis = (zAxisRawValue & 0xfff) - 4097;
|
||||
}
|
||||
zAxis = (zAxis / scale_factor) * gravity;
|
||||
|
||||
// Print results in log
|
||||
LOG.info("READ SENSOR DATA VALUES: counter:"+counter+" step:"+step+" x-axis:"+ String.format("%.03f",xAxis)+" y-axis:"+String.format("%.03f",yAxis)+" z-axis:"+String.format("%.03f",zAxis)+";");
|
||||
}
|
||||
LOG.info("READ SENSOR DATA VALUES: counter:"+counter+" step:"+step+" axis1:"+axis1+" axis2:"+axis2+" axis3:"+axis3+";");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -19,8 +19,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2;
|
||||
public enum FirmwareType {
|
||||
FIRMWARE((byte) 0),
|
||||
FONT((byte) 1),
|
||||
UNKNOWN1((byte) 2),
|
||||
UNKNOWN2((byte) 3),
|
||||
RES((byte) 2),
|
||||
GPS((byte) 3),
|
||||
INVALID(Byte.MIN_VALUE);
|
||||
|
||||
private final byte value;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -53,7 +53,7 @@ public class Mi2FirmwareInfo {
|
||||
0x4b
|
||||
};
|
||||
|
||||
private static Map<Integer,String> crcToVersion = new HashMap<>();
|
||||
protected static Map<Integer,String> crcToVersion = new HashMap<>();
|
||||
static {
|
||||
// firmware
|
||||
crcToVersion.put(41899, "1.0.0.39");
|
||||
@ -89,7 +89,7 @@ public class Mi2FirmwareInfo {
|
||||
firmwareType = determineFirmwareType(bytes);
|
||||
}
|
||||
|
||||
private FirmwareType determineFirmwareType(byte[] bytes) {
|
||||
protected FirmwareType determineFirmwareType(byte[] bytes) {
|
||||
if (ArrayUtils.startsWith(bytes, FT_HEADER)) {
|
||||
return FirmwareType.FONT;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -18,7 +18,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
|
||||
@ -67,11 +66,11 @@ public class Mi2TextNotificationStrategy extends Mi2NotificationStrategy {
|
||||
if (simpleNotification != null) {
|
||||
switch (simpleNotification.getAlertCategory()) {
|
||||
case Email:
|
||||
return new byte[] { BLETypeConversions.fromUint8(MiBand2Service.ALERT_LEVEL_MESSAGE), BLETypeConversions.fromUint8(numAlerts)};
|
||||
return new byte[] { BLETypeConversions.fromUint8(AlertCategory.Email.getId()), BLETypeConversions.fromUint8(numAlerts)};
|
||||
case InstantMessage:
|
||||
return new byte[] { BLETypeConversions.fromUint8(MiBand2Service.ALERT_LEVEL_CUSTOM), BLETypeConversions.fromUint8(numAlerts), MiBand2Service.ICON_CHAT};
|
||||
return new byte[] { BLETypeConversions.fromUint8(AlertCategory.CustomMiBand2.getId()), BLETypeConversions.fromUint8(numAlerts), MiBand2Service.ICON_CHAT};
|
||||
case News:
|
||||
return new byte[] { BLETypeConversions.fromUint8(MiBand2Service.ALERT_LEVEL_CUSTOM), BLETypeConversions.fromUint8(numAlerts), MiBand2Service.ICON_PENGUIN};
|
||||
return new byte[] { BLETypeConversions.fromUint8(AlertCategory.CustomMiBand2.getId()), BLETypeConversions.fromUint8(numAlerts), MiBand2Service.ICON_PENGUIN};
|
||||
}
|
||||
}
|
||||
return new byte[] { BLETypeConversions.fromUint8(AlertCategory.SMS.getId()), BLETypeConversions.fromUint8(numAlerts)};
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Christian
|
||||
Fischer, Daniele Gobbetti, JohnnySun, Julien Pivotto, Kasha, Sergey Trofimov,
|
||||
Steffen Liebergeld
|
||||
Fischer, Daniele Gobbetti, JohnnySun, José Rebelo, Julien Pivotto, Kasha,
|
||||
Sergey Trofimov, Steffen Liebergeld
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -36,8 +36,10 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -50,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInf
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
|
||||
@ -298,7 +301,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
return this;
|
||||
}
|
||||
|
||||
private NotificationStrategy getNotificationStrategy() {
|
||||
public NotificationStrategy getNotificationStrategy() {
|
||||
String firmwareVersion = getDevice().getFirmwareVersion();
|
||||
if (firmwareVersion != null) {
|
||||
Version ver = new Version(firmwareVersion);
|
||||
@ -432,7 +435,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
}
|
||||
|
||||
private void performPreferredNotification(String task, String notificationOrigin, SimpleNotification simpleNotification, int alertLevel, BtLEAction extraAction) {
|
||||
protected void performPreferredNotification(String task, String notificationOrigin, SimpleNotification simpleNotification, int alertLevel, BtLEAction extraAction) {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized(task);
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
@ -526,7 +529,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
performPreferredNotification(origin + " received", origin, simpleNotification, alertLevel, null);
|
||||
}
|
||||
|
||||
private void onAlarmClock(NotificationSpec notificationSpec) {
|
||||
protected void onAlarmClock(NotificationSpec notificationSpec) {
|
||||
alarmClockRinging = true;
|
||||
AbortTransactionAction abortAction = new StopNotificationAction(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL)) {
|
||||
@Override
|
||||
@ -805,7 +808,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleButtonPressed(byte[] value) {
|
||||
public void handleButtonPressed(byte[] value) {
|
||||
LOG.info("Button pressed");
|
||||
logMessageContent(value);
|
||||
}
|
||||
@ -1075,12 +1078,35 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
case MiBandConst.PREF_MI2_DATEFORMAT:
|
||||
setDateDisplay(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_GOAL_NOTIFICATION:
|
||||
setGoalNotification(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT:
|
||||
setActivateDisplayOnLiftWrist(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_DISPLAY_ITEMS:
|
||||
setDisplayItems(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO:
|
||||
setRotateWristToSwitchInfo(builder);
|
||||
break;
|
||||
case ActivityUser.PREF_USER_STEPS_GOAL:
|
||||
setFitnessGoal(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_DO_NOT_DISTURB:
|
||||
case MiBandConst.PREF_MI2_DO_NOT_DISTURB_START:
|
||||
case MiBandConst.PREF_MI2_DO_NOT_DISTURB_END:
|
||||
setDoNotDisturb(builder);
|
||||
break;
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS:
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD:
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_START:
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_END:
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND:
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND_START:
|
||||
case MiBandConst.PREF_MI2_INACTIVITY_WARNINGS_DND_END:
|
||||
setInactivityWarnings(builder);
|
||||
break;
|
||||
}
|
||||
builder.queue(getQueue());
|
||||
} catch (IOException e) {
|
||||
@ -1128,6 +1154,17 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setGoalNotification(TransactionBuilder builder) {
|
||||
boolean enable = MiBand2Coordinator.getGoalNotification();
|
||||
LOG.info("Setting goal notification to " + enable);
|
||||
if (enable) {
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_ENABLE_GOAL_NOTIFICATION);
|
||||
} else {
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DISABLE_GOAL_NOTIFICATION);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setActivateDisplayOnLiftWrist(TransactionBuilder builder) {
|
||||
boolean enable = MiBand2Coordinator.getActivateDisplayOnLiftWrist();
|
||||
LOG.info("Setting activate display on lift wrist to " + enable);
|
||||
@ -1139,6 +1176,137 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setDisplayItems(TransactionBuilder builder) {
|
||||
Set<String> pages = MiBand2Coordinator.getDisplayItems();
|
||||
LOG.info("Setting display items to " + (pages == null ? "none" : pages));
|
||||
|
||||
byte[] data = MiBand2Service.COMMAND_CHANGE_SCREENS.clone();
|
||||
|
||||
if (pages != null) {
|
||||
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_STEPS)) {
|
||||
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_STEPS;
|
||||
}
|
||||
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_DISTANCE)) {
|
||||
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_DISTANCE;
|
||||
}
|
||||
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_CALORIES)) {
|
||||
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_CALORIES;
|
||||
}
|
||||
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_HEART_RATE)) {
|
||||
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_HEART_RATE;
|
||||
}
|
||||
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_BATTERY)) {
|
||||
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_BATTERY;
|
||||
}
|
||||
}
|
||||
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setRotateWristToSwitchInfo(TransactionBuilder builder) {
|
||||
boolean enable = MiBand2Coordinator.getRotateWristToSwitchInfo();
|
||||
LOG.info("Setting rotate wrist to cycle info to " + enable);
|
||||
if (enable) {
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_ENABLE_ROTATE_WRIST_TO_SWITCH_INFO);
|
||||
} else {
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DISABLE_ROTATE_WRIST_TO_SWITCH_INFO);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setDisplayCaller(TransactionBuilder builder) {
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_ENABLE_DISPLAY_CALLER);
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setDoNotDisturb(TransactionBuilder builder) {
|
||||
DoNotDisturb doNotDisturb = MiBand2Coordinator.getDoNotDisturb(getContext());
|
||||
LOG.info("Setting do not disturb to " + doNotDisturb);
|
||||
switch (doNotDisturb) {
|
||||
case OFF:
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DO_NOT_DISTURB_OFF);
|
||||
break;
|
||||
case AUTOMATIC:
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DO_NOT_DISTURB_AUTOMATIC);
|
||||
break;
|
||||
case SCHEDULED:
|
||||
byte[] data = MiBand2Service.COMMAND_DO_NOT_DISTURB_SCHEDULED.clone();
|
||||
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
|
||||
Date start = MiBand2Coordinator.getDoNotDisturbStart();
|
||||
calendar.setTime(start);
|
||||
data[MiBand2Service.DND_BYTE_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.DND_BYTE_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
Date end = MiBand2Coordinator.getDoNotDisturbEnd();
|
||||
calendar.setTime(end);
|
||||
data[MiBand2Service.DND_BYTE_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.DND_BYTE_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setInactivityWarnings(TransactionBuilder builder) {
|
||||
boolean enable = MiBand2Coordinator.getInactivityWarnings();
|
||||
LOG.info("Setting inactivity warnings to " + enable);
|
||||
|
||||
if (enable) {
|
||||
byte[] data = MiBand2Service.COMMAND_ENABLE_INACTIVITY_WARNINGS.clone();
|
||||
|
||||
int threshold = MiBand2Coordinator.getInactivityWarningsThreshold();
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_THRESHOLD] = (byte) threshold;
|
||||
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
|
||||
boolean enableDnd = MiBand2Coordinator.getInactivityWarningsDnd();
|
||||
|
||||
Date intervalStart = MiBand2Coordinator.getInactivityWarningsStart();
|
||||
Date intervalEnd = MiBand2Coordinator.getInactivityWarningsEnd();
|
||||
Date dndStart = MiBand2Coordinator.getInactivityWarningsDndStart();
|
||||
Date dndEnd = MiBand2Coordinator.getInactivityWarningsDndEnd();
|
||||
|
||||
// The first interval always starts when the warnings interval starts
|
||||
calendar.setTime(intervalStart);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
if(enableDnd) {
|
||||
// The first interval ends when the dnd interval starts
|
||||
calendar.setTime(dndStart);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
// The second interval starts when the dnd interval ends
|
||||
calendar.setTime(dndEnd);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_2_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_2_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
|
||||
// ... and it ends when the warnings interval ends
|
||||
calendar.setTime(intervalEnd);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_2_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_2_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
} else {
|
||||
// No Dnd, use the first interval
|
||||
calendar.setTime(intervalEnd);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
|
||||
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
|
||||
}
|
||||
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
|
||||
} else {
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DISABLE_INACTIVITY_WARNINGS);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void phase2Initialize(TransactionBuilder builder) {
|
||||
LOG.info("phase2Initialize...");
|
||||
enableFurtherNotifications(builder, true);
|
||||
@ -1147,7 +1315,13 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
setTimeFormat(builder);
|
||||
setWearLocation(builder);
|
||||
setFitnessGoal(builder);
|
||||
setDisplayItems(builder);
|
||||
setDoNotDisturb(builder);
|
||||
setRotateWristToSwitchInfo(builder);
|
||||
setActivateDisplayOnLiftWrist(builder);
|
||||
setDisplayCaller(builder);
|
||||
setGoalNotification(builder);
|
||||
setInactivityWarnings(builder);
|
||||
setHeartrateSleepSupport(builder);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.util.TimeUtils;
|
||||
import android.text.format.DateUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -66,8 +68,9 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
|
||||
|
||||
private List<MiBandActivitySample> samples = new ArrayList<>(60*24); // 1day per default
|
||||
|
||||
private byte lastPacketCounter = -1;
|
||||
private byte lastPacketCounter;
|
||||
private Calendar startTimestamp;
|
||||
private int fetchCount;
|
||||
|
||||
public FetchActivityOperation(MiBand2Support support) {
|
||||
super(support);
|
||||
@ -83,12 +86,25 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
|
||||
|
||||
@Override
|
||||
protected void doPerform() throws IOException {
|
||||
startFetching();
|
||||
}
|
||||
|
||||
private void startFetching() throws IOException {
|
||||
samples.clear();
|
||||
lastPacketCounter = -1;
|
||||
|
||||
TransactionBuilder builder = performInitialized("fetching activity data");
|
||||
getSupport().setLowLatency(builder);
|
||||
if (fetchCount == 0) {
|
||||
builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
|
||||
}
|
||||
fetchCount++;
|
||||
|
||||
BluetoothGattCharacteristic characteristicActivityData = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA);
|
||||
builder.notify(characteristicActivityData, false);
|
||||
|
||||
BluetoothGattCharacteristic characteristicFetch = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC4);
|
||||
builder.notify(characteristicFetch, true);
|
||||
BluetoothGattCharacteristic characteristicActivityData = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA);
|
||||
|
||||
GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
|
||||
builder.write(characteristicFetch, BLETypeConversions.join(new byte[] { MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE, 0x01 }, getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES)));
|
||||
@ -136,13 +152,40 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
|
||||
}
|
||||
|
||||
private void handleActivityFetchFinish() {
|
||||
LOG.info("Fetching activity data has finished.");
|
||||
saveSamples();
|
||||
LOG.info("Fetching activity data has finished round " + fetchCount);
|
||||
GregorianCalendar lastSyncTimestamp = saveSamples();
|
||||
if (lastSyncTimestamp != null && needsAnotherFetch(lastSyncTimestamp)) {
|
||||
try {
|
||||
startFetching();
|
||||
return;
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Error starting another round of fetching activity data", ex);
|
||||
}
|
||||
}
|
||||
|
||||
operationFinished();
|
||||
unsetBusy();
|
||||
}
|
||||
|
||||
private void saveSamples() {
|
||||
private boolean needsAnotherFetch(GregorianCalendar lastSyncTimestamp) {
|
||||
if (fetchCount > 5) {
|
||||
LOG.warn("Already jave 5 fetch rounds, not doing another one.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DateUtils.isToday(lastSyncTimestamp.getTimeInMillis())) {
|
||||
LOG.info("Hopefully no further fetch needed, last synced timestamp is from today.");
|
||||
return false;
|
||||
}
|
||||
if (lastSyncTimestamp.getTimeInMillis() > System.currentTimeMillis()) {
|
||||
LOG.warn("Not doing another fetch since last synced timestamp is in the future: " + DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime()));
|
||||
return false;
|
||||
}
|
||||
LOG.info("Doing another fetch since last sync timestamp is still too old: " + DateTimeUtils.formatDateTime(lastSyncTimestamp.getTime()));
|
||||
return true;
|
||||
}
|
||||
|
||||
private GregorianCalendar saveSamples() {
|
||||
if (samples.size() > 0) {
|
||||
// save all the samples that we got
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
@ -168,6 +211,7 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
|
||||
|
||||
saveLastSyncTimestamp(timestamp);
|
||||
LOG.info("Mi2 activity data: last sample timestamp: " + DateTimeUtils.formatDateTime(timestamp.getTime()));
|
||||
return timestamp;
|
||||
|
||||
} catch (Exception ex) {
|
||||
GB.toast(getContext(), "Error saving activity samples", Toast.LENGTH_LONG, GB.ERROR);
|
||||
@ -175,6 +219,7 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
|
||||
samples.clear();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -48,11 +48,11 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
public class UpdateFirmwareOperation extends AbstractMiBand2Operation {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpdateFirmwareOperation.class);
|
||||
|
||||
private final Uri uri;
|
||||
private final BluetoothGattCharacteristic fwCControlChar;
|
||||
private final BluetoothGattCharacteristic fwCDataChar;
|
||||
final Prefs prefs = GBApplication.getPrefs();
|
||||
private Mi2FirmwareInfo firmwareInfo;
|
||||
protected final Uri uri;
|
||||
protected final BluetoothGattCharacteristic fwCControlChar;
|
||||
protected final BluetoothGattCharacteristic fwCDataChar;
|
||||
protected final Prefs prefs = GBApplication.getPrefs();
|
||||
protected Mi2FirmwareInfo firmwareInfo;
|
||||
|
||||
public UpdateFirmwareOperation(Uri uri, MiBand2Support support) {
|
||||
super(support);
|
||||
@ -82,7 +82,7 @@ public class UpdateFirmwareOperation extends AbstractMiBand2Operation {
|
||||
//the firmware will be sent by the notification listener if the band confirms that the metadata are ok.
|
||||
}
|
||||
|
||||
private void done() {
|
||||
protected void done() {
|
||||
LOG.info("Operation done.");
|
||||
operationFinished();
|
||||
unsetBusy();
|
||||
@ -163,7 +163,7 @@ public class UpdateFirmwareOperation extends AbstractMiBand2Operation {
|
||||
done();
|
||||
}
|
||||
}
|
||||
private void displayMessage(Context context, String message, int duration, int severity) {
|
||||
protected void displayMessage(Context context, String message, int duration, int severity) {
|
||||
getSupport().handleGBDeviceEvent(new GBDeviceEventDisplayMessage(message, duration, severity));
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,6 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -158,9 +157,11 @@ class AppMessageHandlerMorpheuz extends AppMessageHandler {
|
||||
int version = (int) pair.second;
|
||||
LOG.info("got version: " + ((float) version / 10.0f));
|
||||
ctrl_message |= CTRL_VERSION_DONE;
|
||||
} else if (pair.first.equals(keyBase)) {// fix timestamp
|
||||
TimeZone tz = SimpleTimeZone.getDefault();
|
||||
recording_base_timestamp = (int) pair.second - (tz.getOffset(System.currentTimeMillis())) / 1000;
|
||||
} else if (pair.first.equals(keyBase)) {
|
||||
recording_base_timestamp = (int) pair.second;
|
||||
if (mPebbleProtocol.mFwMajor < 3) {
|
||||
recording_base_timestamp -= SimpleTimeZone.getDefault().getOffset(recording_base_timestamp * 1000L) / 1000;
|
||||
}
|
||||
LOG.info("got base: " + recording_base_timestamp);
|
||||
ctrl_message |= CTRL_SET_LAST_SENT | CTRL_DO_NEXT;
|
||||
} else if (pair.first.equals(keyAutoReset)) {
|
||||
|
@ -156,7 +156,7 @@ class PebbleIoThread extends GBDeviceIoThread {
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOG.warn("error while connecting: " + e.getMessage(), e);
|
||||
gbDevice.setState(originalState);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
|
||||
|
@ -87,6 +87,8 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
|
||||
byteArray[i] = ((Integer) jsonArray.get(i)).byteValue();
|
||||
}
|
||||
object = byteArray;
|
||||
} else if (object instanceof Boolean) {
|
||||
object = (short) (((Boolean) object) ? 1 : 0);
|
||||
}
|
||||
pairs.add(new Pair<>(Integer.parseInt(keyStr), object));
|
||||
}
|
||||
|
@ -164,7 +164,14 @@ class PebbleGATTClient extends BluetoothGattCallback {
|
||||
if (doPairing) {
|
||||
BluetoothGattCharacteristic characteristic = gatt.getService(SERVICE_UUID).getCharacteristic(PAIRING_TRIGGER_CHARACTERISTIC);
|
||||
if ((characteristic.getProperties() & PROPERTY_WRITE) != 0) {
|
||||
characteristic.setValue(new byte[]{1});
|
||||
LOG.info("This seems to be a >=4.0 FW Pebble, writing to pairing trigger");
|
||||
// flags:
|
||||
// 0 - always 1
|
||||
// 1 - unknown
|
||||
// 2 - always 0
|
||||
// 3 - unknown, set on kitkat (seems to help to get a "better" pairing)
|
||||
// 4 - unknown, set on some phones
|
||||
characteristic.setValue(new byte[]{9});
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
} else {
|
||||
LOG.info("This seems to be some <4.0 FW Pebble, reading pairing trigger");
|
||||
|
@ -39,6 +39,7 @@ public class GBCallControlReceiver extends BroadcastReceiver {
|
||||
GBDeviceEventCallControl.Event callCmd = GBDeviceEventCallControl.Event.values()[intent.getIntExtra("event", 0)];
|
||||
switch (callCmd) {
|
||||
case END:
|
||||
case REJECT:
|
||||
case START:
|
||||
try {
|
||||
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
@ -46,7 +47,7 @@ public class GBCallControlReceiver extends BroadcastReceiver {
|
||||
Method method = clazz.getDeclaredMethod("getITelephony");
|
||||
method.setAccessible(true);
|
||||
ITelephony telephonyService = (ITelephony) method.invoke(telephonyManager);
|
||||
if (callCmd == GBDeviceEventCallControl.Event.END) {
|
||||
if (callCmd == GBDeviceEventCallControl.Event.END || callCmd == GBDeviceEventCallControl.Event.REJECT) {
|
||||
telephonyService.endCall();
|
||||
} else {
|
||||
telephonyService.answerRingingCall();
|
||||
|
@ -16,12 +16,20 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.os.ParcelUuid;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class AndroidUtils {
|
||||
public static ParcelUuid[] toParcelUUids(Parcelable[] uuids) {
|
||||
if (uuids == null) {
|
||||
@ -61,4 +69,48 @@ public class AndroidUtils {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLanguage(Activity activity, Locale language) {
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(language);
|
||||
|
||||
// FIXME: I have no idea what I am doing
|
||||
activity.getBaseContext().getResources().updateConfiguration(config, activity.getBaseContext().getResources().getDisplayMetrics());
|
||||
activity.recreate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the theme dependent text color as a css-style hex string.
|
||||
* @param context the context to access the colour
|
||||
*/
|
||||
public static String getTextColorHex(Context context) {
|
||||
int color;
|
||||
if (GBApplication.isDarkThemeEnabled()) {
|
||||
color = context.getResources().getColor(R.color.primarytext_dark);
|
||||
} else {
|
||||
color = context.getResources().getColor(R.color.primarytext_light);
|
||||
}
|
||||
return colorToHex(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the theme dependent background color as a css-style hex string.
|
||||
* @param context the context to access the colour
|
||||
*/
|
||||
public static String getBackgroundColorHex(Context context) {
|
||||
int color;
|
||||
if (GBApplication.isDarkThemeEnabled()) {
|
||||
color = context.getResources().getColor(R.color.cardview_dark_background);
|
||||
} else {
|
||||
color = context.getResources().getColor(R.color.cardview_light_background);
|
||||
}
|
||||
return colorToHex(color);
|
||||
}
|
||||
|
||||
private static String colorToHex(int color) {
|
||||
return "#"
|
||||
+ Integer.toHexString(Color.red(color))
|
||||
+ Integer.toHexString(Color.green(color))
|
||||
+ Integer.toHexString(Color.blue(color));
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipCooordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.MakibesF68Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewCoordinator;
|
||||
@ -184,6 +185,7 @@ public class DeviceHelper {
|
||||
|
||||
private List<DeviceCoordinator> createCoordinators() {
|
||||
List<DeviceCoordinator> result = new ArrayList<>();
|
||||
result.add(new AmazfitBipCooordinator()); // Note: AmazfitBip must come before MiBand2 because detection is hacky, atm
|
||||
result.add(new MiBand2Coordinator()); // Note: MiBand2 must come before MiBand because detection is hacky, atm
|
||||
result.add(new MiBandCoordinator());
|
||||
result.add(new PebbleCoordinator());
|
||||
|
@ -76,7 +76,7 @@ public class GB {
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(true);
|
||||
if (GBApplication.isRunningLollipopOrLater()) {
|
||||
builder.setVisibility(Notification.VISIBILITY_PUBLIC);
|
||||
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
|
||||
}
|
||||
if (GBApplication.minimizeNotification()) {
|
||||
builder.setPriority(Notification.PRIORITY_MIN);
|
||||
@ -268,7 +268,7 @@ public class GB {
|
||||
notificationIntent, 0);
|
||||
|
||||
NotificationCompat.Builder nb = new NotificationCompat.Builder(context)
|
||||
.setVisibility(Notification.VISIBILITY_PUBLIC)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentTitle(context.getString(R.string.app_name))
|
||||
.setContentText(text)
|
||||
.setContentIntent(pendingIntent)
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -21,6 +21,7 @@ import java.util.Date;
|
||||
|
||||
public class GBPrefs {
|
||||
public static final String PACKAGE_BLACKLIST = "package_blacklist";
|
||||
public static final String CALENDAR_BLACKLIST = "calendar_blacklist";
|
||||
public static final String AUTO_RECONNECT = "general_autocreconnect";
|
||||
private static final String AUTO_START = "general_autostartonboot";
|
||||
private static final boolean AUTO_START_DEFAULT = true;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017 Alberto, Carsten Pfeiffer
|
||||
/* Copyright (C) 2017 Alberto, Carsten Pfeiffer, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -17,6 +17,12 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Xml;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
@ -27,12 +33,6 @@ import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Xml;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
|
||||
public class ImportExportSharedPreferences {
|
||||
@ -125,12 +125,19 @@ public class ImportExportSharedPreferences {
|
||||
editor.putString(key, text);
|
||||
} else if (HASHSET.equals(name)) {
|
||||
if (key.equals(GBPrefs.PACKAGE_BLACKLIST)) {
|
||||
Set<String> blacklist = new HashSet<>();
|
||||
Set<String> apps_blacklist = new HashSet<>();
|
||||
text=text.replace("[","").replace("]","");
|
||||
for (int z=0;z<text.split(",").length;z++){
|
||||
blacklist.add(text.split(",")[z].trim());
|
||||
apps_blacklist.add(text.split(",")[z].trim());
|
||||
}
|
||||
GBApplication.setBlackList(blacklist);
|
||||
GBApplication.setAppsBlackList(apps_blacklist);
|
||||
} else if (key.equals(GBPrefs.CALENDAR_BLACKLIST)) { //TODO: untested
|
||||
Set<String> calendars_blacklist = new HashSet<>();
|
||||
text = text.replace("[", "").replace("]", "");
|
||||
for (int z = 0; z < text.split(",").length; z++) {
|
||||
calendars_blacklist.add(text.split(",")[z].trim());
|
||||
}
|
||||
GBApplication.setCalendarsBlackList(calendars_blacklist);
|
||||
}
|
||||
} else if (!PREFERENCES.equals(name)) {
|
||||
throw new Exception("Unkown type " + name);
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2017 ivanovlev, Yaron Shahrabani
|
||||
/* Copyright (C) 2017 Andreas Shimokawa, ivanovlev, lazarosfs, Yaron
|
||||
Shahrabani
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -28,7 +29,12 @@ public class LanguageUtils {
|
||||
private static Map<Character, String> transliterateMap = new HashMap<Character, String>(){
|
||||
{
|
||||
//extended ASCII characters
|
||||
put('æ', "ae"); put('œ', "oe"); put('ß', "B"); put('ª', "a"); put('º', "o"); put('«',"\""); put('»',"\"");
|
||||
put('æ', "ae"); put('œ', "oe"); put('ª', "a"); put('º', "o"); put('«',"\""); put('»',"\"");
|
||||
|
||||
//german characters
|
||||
put('ä',"ae"); put('ö',"oe"); put('ü',"ue");
|
||||
put('Ä',"Ae"); put('Ö',"Oe"); put('Ü',"Üe");
|
||||
put('ß',"ss"); put('ẞ',"SS");
|
||||
|
||||
//russian chars
|
||||
put('а', "a"); put('б', "b"); put('в', "v"); put('г', "g"); put('д', "d"); put('е', "e"); put('ё', "jo"); put('ж', "zh");
|
||||
@ -42,7 +48,19 @@ public class LanguageUtils {
|
||||
put('ט', "t"); put('י', "y"); put('כ', "c"); put('ל', "l"); put('מ', "m"); put('נ', "n"); put('ס', "s"); put('ע', "'");
|
||||
put('פ', "p"); put('צ', "ts"); put('ק', "k"); put('ר', "r"); put('ש', "sh"); put('ת', "th"); put('ף', "f"); put('ץ', "ts");
|
||||
put('ך', "ch");put('ם', "m");put('ן', "n");
|
||||
//continue for other languages...
|
||||
|
||||
// greek chars
|
||||
put('α',"a");put('ά',"a");put('β',"v");put('γ',"g");put('δ',"d");put('ε',"e");put('έ',"e");put('ζ',"z");put('η',"i");
|
||||
put('ή',"i");put('θ',"th");put('ι',"i");put('ί',"i");put('ϊ',"i");put('ΐ',"i");put('κ',"k");put('λ',"l");put('μ',"m");
|
||||
put('ν',"n");put('ξ',"ks");put('ο',"o");put('ό',"o");put('π',"p");put('ρ',"r");put('σ',"s");put('ς',"s");put('τ',"t");
|
||||
put('υ',"y");put('ύ',"y");put('ϋ',"y");put('ΰ',"y");put('φ',"f");put('χ',"ch");put('ψ',"ps");put('ω',"o");put('ώ',"o");
|
||||
put('Α',"A");put('Ά',"A");put('Β',"B");put('Γ',"G");put('Δ',"D");put('Ε',"E");put('Έ',"E");put('Ζ',"Z");put('Η',"I");
|
||||
put('Ή',"I");put('Θ',"TH");put('Ι',"I");put('Ί',"I");put('Ϊ',"I");put('Κ',"K");put('Λ',"L");put('Μ',"M");put('Ν',"N");
|
||||
put('Ξ',"KS");put('Ο',"O");put('Ό',"O");put('Π',"P");put('Ρ',"R");put('Σ',"S");put('Τ',"T");put('Υ',"Y");put('Ύ',"Y");
|
||||
put('Ϋ',"Y");put('Φ',"F");put('Χ',"CH");put('Ψ',"PS");put('Ω',"O");put('Ώ',"O");
|
||||
|
||||
//TODO: these must be configurabe. If someone wants to transliterate cyrillic it does not mean his device has no German umlauts
|
||||
//all or nothing is really bad here
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -162,4 +163,16 @@ public class Prefs {
|
||||
public SharedPreferences getPreferences() {
|
||||
return preferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ugly workaround for Set<String> preferences not consistently applying.
|
||||
* @param editor
|
||||
* @param preference
|
||||
* @param value
|
||||
*/
|
||||
public static void putStringSet(SharedPreferences.Editor editor, String preference, HashSet<String> value) {
|
||||
editor.putStringSet(preference, null);
|
||||
editor.commit();
|
||||
editor.putStringSet(preference, new HashSet<>(value));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,116 @@
|
||||
/* Copyright (C) 2017 José Rebelo
|
||||
|
||||
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.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.preference.DialogPreference;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.TimePicker;
|
||||
|
||||
public class TimePreference extends DialogPreference {
|
||||
private int hour = 0;
|
||||
private int minute = 0;
|
||||
|
||||
private TimePicker picker = null;
|
||||
|
||||
public TimePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateDialogView() {
|
||||
picker = new TimePicker(getContext());
|
||||
picker.setIs24HourView(DateFormat.is24HourFormat(getContext()));
|
||||
picker.setPadding(0, 50, 0, 50);
|
||||
|
||||
return picker;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(View v) {
|
||||
super.onBindDialogView(v);
|
||||
|
||||
picker.setCurrentHour(hour);
|
||||
picker.setCurrentMinute(minute);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
super.onDialogClosed(positiveResult);
|
||||
|
||||
if (positiveResult) {
|
||||
hour = picker.getCurrentHour();
|
||||
minute = picker.getCurrentMinute();
|
||||
|
||||
String time = getTime24h();
|
||||
|
||||
if (callChangeListener(time)) {
|
||||
persistString(time);
|
||||
|
||||
updateSummary();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object onGetDefaultValue(TypedArray a, int index) {
|
||||
return a.getString(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
|
||||
String time;
|
||||
|
||||
if (restoreValue) {
|
||||
if (defaultValue == null) {
|
||||
time = getPersistedString("00:00");
|
||||
} else {
|
||||
time = getPersistedString(defaultValue.toString());
|
||||
}
|
||||
} else {
|
||||
time = defaultValue.toString();
|
||||
}
|
||||
|
||||
String[] pieces = time.split(":");
|
||||
|
||||
hour = Integer.parseInt(pieces[0]);
|
||||
minute = Integer.parseInt(pieces[1]);
|
||||
|
||||
updateSummary();
|
||||
}
|
||||
|
||||
public void updateSummary() {
|
||||
if (DateFormat.is24HourFormat(getContext()))
|
||||
setSummary(getTime24h());
|
||||
else
|
||||
setSummary(getTime12h());
|
||||
}
|
||||
|
||||
public String getTime24h() {
|
||||
return String.format("%02d", hour) + ":" + String.format("%02d", minute);
|
||||
}
|
||||
|
||||
public String getTime12h() {
|
||||
String suffix = hour < 12 ? " AM" : " PM";
|
||||
int h = hour > 12 ? hour - 12 : hour;
|
||||
|
||||
return String.valueOf(h) + ":" + String.format("%02d", minute) + suffix;
|
||||
}
|
||||
}
|
@ -28,6 +28,8 @@ public class Version implements Comparable<Version> {
|
||||
public Version(String version) {
|
||||
if(version == null)
|
||||
throw new IllegalArgumentException("Version can not be null");
|
||||
|
||||
version = version.trim();
|
||||
if (!version.matches("[0-9]+(\\.[0-9]+)*"))
|
||||
throw new IllegalArgumentException("Invalid version format");
|
||||
this.version = version;
|
||||
|
BIN
app/src/main/res/drawable-hdpi/ic_device_default.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.8 KiB |