1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-28 12:56:49 +01:00

Merge branch 'master' into background-javascript

This commit is contained in:
Andreas Shimokawa 2017-08-22 20:13:20 +02:00
commit f4e11c8cb3
28 changed files with 562 additions and 34 deletions

View File

@ -1,6 +1,10 @@
### Changelog ### Changelog
#### Version 0.20.0 (next) #### 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) * Inital Amazfit Bip support (WIP)
* Various theming fixes * Various theming fixes
* Add workaround for blacklist not properly persisting * Add workaround for blacklist not properly persisting
@ -8,7 +12,7 @@
* Pebble: Pass booleans from Javascript Appmessage correctly * Pebble: Pass booleans from Javascript Appmessage correctly
* Pebble: Make local configuration pages work on most recent webview implementation * Pebble: Make local configuration pages work on most recent webview implementation
* Pebble: Allow to blacklist calendars * Pebble: Allow to blacklist calendars
* Add greek transliteration support * Add Greek and German transliteration support
* Various visual improvements to charts * Various visual improvements to charts
#### Version 0.19.4 #### Version 0.19.4

View File

@ -22,3 +22,6 @@ Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0):
Creative Commons Attribution 3.0 Unported license (CC BY-3.0): 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 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/

View File

@ -2,8 +2,8 @@ Gadgetbridge
============ ============
Gadgetbridge is an Android (4.4+) application which will allow you to use your Gadgetbridge is an Android (4.4+) application which will allow you to use your
Pebble or Mi Band or HPlus device without the vendor's closed source application 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 and without the need to create an account and transmit any of your data to the
vendor's servers. vendor's servers.
[Homepage](https://gadgetbridge.org) [Homepage](https://gadgetbridge.org)
@ -23,6 +23,7 @@ vendor's servers.
* 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 * 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, 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 * 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
* 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) * HPlus Devices (e.g. ZeBand) [Wiki section about this device](https://github.com/Freeyourgadget/Gadgetbridge/wiki/HPlus)
* Liveview * Liveview
* Vibratissimo (experimental) * Vibratissimo (experimental)

View File

@ -26,8 +26,8 @@ android {
targetSdkVersion 25 targetSdkVersion 25
// note: always bump BOTH versionCode and versionName! // note: always bump BOTH versionCode and versionName!
versionName "0.20.0" versionName "0.20.1"
versionCode 98 versionCode 99
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
buildTypes { buildTypes {

View File

@ -25,6 +25,7 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -61,8 +62,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static de.cketti.library.changelog.ChangeLog.DEFAULT_CSS;
//TODO: extend GBActivity, but it requires actionbar that is not available //TODO: extend GBActivity, but it requires actionbar that is not available
public class ControlCenterv2 extends AppCompatActivity public class ControlCenterv2 extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener { implements NavigationView.OnNavigationItemSelectedListener {
@ -250,6 +249,11 @@ public class ControlCenterv2 extends AppCompatActivity
case R.id.action_quit: case R.id.action_quit:
GBApplication.quit(); GBApplication.quit();
return true; 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: case R.id.external_changelog:
ChangeLog cl = createChangeLog(); ChangeLog cl = createChangeLog();
cl.getFullLogDialog().show(); cl.getFullLogDialog().show();

View File

@ -189,6 +189,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
public void onClick(View v) { public void onClick(View v) {
Intent startIntent; Intent startIntent;
startIntent = new Intent(context, ConfigureAlarms.class); startIntent = new Intent(context, ConfigureAlarms.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent); context.startActivity(startIntent);
} }
} }

View File

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

View File

@ -0,0 +1,24 @@
/* 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 java.util.UUID;
public class AmazfitBipService {
public static final UUID UUID_CHARACTERISTIC_WEATHER = UUID.fromString("0000000e-0000-3512-2118-0009af100700");
}

View File

@ -0,0 +1,189 @@
/* 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;
}
}
public static String mapToOpenWeatherMapIcon(int openWeatherMapCondition) {
//see https://openweathermap.org/weather-conditions
String condition = "02d"; //generic "variable" icon
if (openWeatherMapCondition >= 200 && openWeatherMapCondition < 300) {
condition = "11d";
} else if (openWeatherMapCondition >= 300 && openWeatherMapCondition < 500) {
condition = "09d";
} else if (openWeatherMapCondition >= 500 && openWeatherMapCondition < 510) {
condition = "10d";
} else if (openWeatherMapCondition >= 511 && openWeatherMapCondition < 600) {
condition = "09d";
} else if (openWeatherMapCondition >= 600 && openWeatherMapCondition < 700) {
condition = "13d";
} else if (openWeatherMapCondition >= 700 && openWeatherMapCondition < 800) {
condition = "50d";
} else if (openWeatherMapCondition == 800) {
condition = "01d"; //TODO: night?
} else if (openWeatherMapCondition == 801) {
condition = "02d"; //TODO: night?
} else if (openWeatherMapCondition == 802) {
condition = "03d"; //TODO: night?
} else if (openWeatherMapCondition == 803 || openWeatherMapCondition == 804) {
condition = "04d"; //TODO: night?
}
return condition;
}
}

View File

@ -23,6 +23,8 @@ import android.content.Intent;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.SimpleTimeZone;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.Weather; import nodomain.freeyourgadget.gadgetbridge.model.Weather;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
@ -51,7 +53,7 @@ public class WeatherNotificationReceiver extends BroadcastReceiver {
LOG.info("weather in " + weather.location + " is " + weather.currentCondition + " (" + (weather.currentTemp - 273) + "°C)"); LOG.info("weather in " + weather.location + " is " + weather.currentCondition + " (" + (weather.currentTemp - 273) + "°C)");
WeatherSpec weatherSpec = new WeatherSpec(); WeatherSpec weatherSpec = new WeatherSpec();
weatherSpec.timestamp = (int) (weather.queryTime / 1000); weatherSpec.timestamp = (int) ((weather.queryTime - SimpleTimeZone.getDefault().getOffset(weather.queryTime)) / 1000);
weatherSpec.location = weather.location; weatherSpec.location = weather.location;
weatherSpec.currentTemp = weather.currentTemp; weatherSpec.currentTemp = weather.currentTemp;
weatherSpec.currentCondition = weather.currentCondition; weatherSpec.currentCondition = weather.currentCondition;

View File

@ -37,7 +37,8 @@ public enum AlertCategory {
// 10-250 reserved for future use // 10-250 reserved for future use
// 251-255 defined by service specification // 251-255 defined by service specification
Any(255), Any(255),
Custom(-1); Custom(-1),
CustomAmazfitBip(-6);
private final int id; private final int id;

View File

@ -33,12 +33,16 @@ import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class AlertNotificationProfile<T extends AbstractBTLEDeviceSupport> extends AbstractBleProfile<T> { public class AlertNotificationProfile<T extends AbstractBTLEDeviceSupport> extends AbstractBleProfile<T> {
private static final Logger LOG = LoggerFactory.getLogger(AlertNotificationProfile.class); 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) { public AlertNotificationProfile(T support) {
super(support); super(support);
} }
public void setMaxLength(int maxLength) {
this.maxLength = maxLength;
}
public void configure(TransactionBuilder builder, AlertNotificationControl control) { public void configure(TransactionBuilder builder, AlertNotificationControl control) {
BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_NOTIFICATION_CONTROL_POINT); BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_NOTIFICATION_CONTROL_POINT);
if (characteristic != null) { if (characteristic != null) {
@ -57,21 +61,21 @@ public class AlertNotificationProfile<T extends AbstractBTLEDeviceSupport> exten
BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_NEW_ALERT); BluetoothGattCharacteristic characteristic = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_NEW_ALERT);
if (characteristic != null) { if (characteristic != null) {
String message = StringUtils.ensureNotNull(alert.getMessage()); String message = StringUtils.ensureNotNull(alert.getMessage());
if (message.length() > MAX_MSG_LENGTH && strategy == OverflowStrategy.TRUNCATE) { if (message.length() > maxLength && strategy == OverflowStrategy.TRUNCATE) {
message = StringUtils.truncate(message, MAX_MSG_LENGTH); message = StringUtils.truncate(message, maxLength);
} }
int numChunks = message.length() / MAX_MSG_LENGTH; int numChunks = message.length() / maxLength;
if (message.length() % MAX_MSG_LENGTH > 0) { if (message.length() % maxLength > 0) {
numChunks++; numChunks++;
} }
try { try {
boolean hasAlerted = false; boolean hasAlerted = false;
for (int i = 0; i < numChunks; i++) { for (int i = 0; i < numChunks; i++) {
int offset = i * MAX_MSG_LENGTH; int offset = i * maxLength;
int restLength = message.length() - offset; 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) { if (hasAlerted && message.length() == 0) {
// no need to do it again when there is no text content // no need to do it again when there is no text content
break; 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 { protected byte[] getAlertMessage(NewAlert alert, String message, int chunk) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream(100); ByteArrayOutputStream stream = new ByteArrayOutputStream(100);
stream.write(BLETypeConversions.fromUint8(alert.getCategory().getId())); stream.write(BLETypeConversions.fromUint8(alert.getCategory().getId()));
stream.write(BLETypeConversions.fromUint8(alert.getNumAlerts())); stream.write(BLETypeConversions.fromUint8(alert.getNumAlerts()));
if (alert.getCategory() == AlertCategory.CustomAmazfitBip) {
stream.write(BLETypeConversions.fromUint8(alert.getCustomIcon()));
}
if (message.length() > 0) { if (message.length() > 0) {
stream.write(BLETypeConversions.toUtf8s(message)); stream.write(BLETypeConversions.toUtf8s(message));

View File

@ -16,6 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification; 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 * 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 AlertCategory category;
private final int numAlerts; private final int numAlerts;
private final String message; private final String message;
private int customIcon = -1;
public NewAlert(AlertCategory category, int /*uint8*/ numAlerts, String /*utf8s*/ message) { public NewAlert(AlertCategory category, int /*uint8*/ numAlerts, String /*utf8s*/ message) {
this.category = category; this.category = category;
@ -54,6 +57,13 @@ public class NewAlert {
this.message = message; 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() { public AlertCategory getCategory() {
return category; return category;
} }
@ -65,4 +75,8 @@ public class NewAlert {
public String getMessage() { public String getMessage() {
return message; return message;
} }
public int getCustomIcon() {
return customIcon;
}
} }

View File

@ -16,17 +16,77 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip; package nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl; 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.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.miband.NotificationStrategy; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class AmazfitBipSupport extends MiBand2Support { public class AmazfitBipSupport extends MiBand2Support {
private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipSupport.class);
@Override @Override
public NotificationStrategy getNotificationStrategy() { public NotificationStrategy getNotificationStrategy() {
return new AmazfitBipTextNotificationStrategy(this); 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.CustomAmazfitBip;
// 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 @Override
public void onFindDevice(boolean start) { public void onFindDevice(boolean start) {
CallSpec callSpec = new CallSpec(); CallSpec callSpec = new CallSpec();
@ -51,4 +111,36 @@ public class AmazfitBipSupport extends MiBand2Support {
} }
evaluateGBDeviceEvent(callCmd); evaluateGBDeviceEvent(callCmd);
} }
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
try {
TransactionBuilder builder = performInitialized("Sending weather forecast");
final byte NR_DAYS = 2;
ByteBuffer buf = ByteBuffer.allocate(7 + 4 * NR_DAYS);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put((byte) 1);
buf.putInt(weatherSpec.timestamp);
buf.put((byte) 0);
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));
condition = AmazfitBipWeatherConditions.mapToAmazfitBipWeatherCode(weatherSpec.tomorrowConditionCode);
buf.put(condition);
buf.put(condition);
buf.put((byte) (weatherSpec.tomorrowMaxTemp - 273));
buf.put((byte) (weatherSpec.tomorrowMinTemp - 273));
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
builder.queue(getQueue());
} catch (IOException ignore) {
}
}
} }

View File

@ -16,15 +16,22 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip; package nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip;
import android.support.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile; import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory; import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.OverflowStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification; import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.Mi2TextNotificationStrategy; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.Mi2TextNotificationStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
// This class in no longer in use except for incoming calls
class AmazfitBipTextNotificationStrategy extends Mi2TextNotificationStrategy { class AmazfitBipTextNotificationStrategy extends Mi2TextNotificationStrategy {
AmazfitBipTextNotificationStrategy(MiBand2Support support) { AmazfitBipTextNotificationStrategy(MiBand2Support support) {
@ -33,14 +40,28 @@ class AmazfitBipTextNotificationStrategy extends Mi2TextNotificationStrategy {
@Override @Override
protected void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, BtLEAction extraAction, TransactionBuilder builder) { protected void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, BtLEAction extraAction, TransactionBuilder builder) {
if (simpleNotification != null && simpleNotification.getAlertCategory() == AlertCategory.IncomingCall) {
// incoming calls are notified solely via NewAlert including caller ID
sendAlert(simpleNotification, builder);
return;
}
if (simpleNotification != null && !StringUtils.isEmpty(simpleNotification.getMessage())) { if (simpleNotification != null && !StringUtils.isEmpty(simpleNotification.getMessage())) {
sendAlert(simpleNotification, builder); 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);
}
} }

View File

@ -435,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 { try {
TransactionBuilder builder = performInitialized(task); TransactionBuilder builder = performInitialized(task);
Prefs prefs = GBApplication.getPrefs(); Prefs prefs = GBApplication.getPrefs();
@ -529,7 +529,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
performPreferredNotification(origin + " received", origin, simpleNotification, alertLevel, null); performPreferredNotification(origin + " received", origin, simpleNotification, alertLevel, null);
} }
private void onAlarmClock(NotificationSpec notificationSpec) { protected void onAlarmClock(NotificationSpec notificationSpec) {
alarmClockRinging = true; alarmClockRinging = true;
AbortTransactionAction abortAction = new StopNotificationAction(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL)) { AbortTransactionAction abortAction = new StopNotificationAction(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL)) {
@Override @Override

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<group android:scaleY="-1">
<group android:translateY="-48">
<path
android:fillColor="#000000"
android:strokeWidth="0.75"
android:strokeMiterLimit="79.8403193612775"
android:strokeLineCap="round"
android:pathData="M 17.266,14.156 C 17.266,16.557 18.748,18.614 20.847,19.466 L 20.749,19.461 C
18.032,21.925 18.249,23.288 18.202,24.301 L 18.202,24.812 C 13.398,21.976
8.006,18.283 6.316,19.973 L 5.892,20.99 C 11.546,24.893 17.202,28.796
21.852,32.704 C 22.249,33.138 22.902,33.403 23.974,33.385 C 28.907,33.336
33.583,32.949 38.07,32.451 L 43.588,34.829 L 43.417,27.782 C 38.887,23.557
32.161,21.394 27.091,18.166 C 28.105,17.132 28.732,15.715 28.732,14.156 C
28.732,10.992 26.164,8.423 22.998,8.423 C 19.835,8.423 17.266,10.992
17.266,14.156 Z M 27.246,14.156 C 27.246,16.496 25.341,18.399 22.998,18.399 C
20.655,18.399 18.752,16.496 18.752,14.156 C 18.752,11.813 20.655,9.911
22.998,9.911 C 25.341,9.911 27.246,11.813 27.246,14.156 Z M 21.197,11.848 C
20.842,12.223 20.608,12.689 20.497,13.249 L 19.885,13.249 L 20.018,13.888 L
20.423,13.888 C 20.42,13.954 20.415,14.027 20.415,14.101 C 20.415,14.229
20.42,14.338 20.426,14.434 L 19.885,14.434 L 20.018,15.075 L 20.509,15.075 C
20.625,15.625 20.854,16.084 21.198,16.452 C 21.721,17.009 22.403,17.285
23.253,17.285 C 23.716,17.285 24.118,17.202 24.458,17.036 L 24.216,15.889 C
23.98,16.124 23.64,16.24 23.19,16.24 C 22.74,16.24 22.378,16.079 22.101,15.756 C
21.943,15.576 21.832,15.347 21.766,15.075 L 24.04,15.075 L 23.905,14.434 L
21.683,14.434 C 21.68,14.373 21.679,14.291 21.679,14.192 C 21.679,14.096
21.68,13.994 21.686,13.888 L 23.791,13.888 L 23.659,13.249 L 21.772,13.249 C
21.842,12.953 21.946,12.721 22.093,12.556 C 22.367,12.23 22.728,12.067
23.166,12.067 C 23.694,12.067 24.117,12.23 24.431,12.556 L 24.431,11.29 C
24.073,11.11 23.656,11.021 23.179,11.021 C 22.375,11.021 21.713,11.298
21.197,11.848 Z M 29.494,23.708 C 30.203,25.42 30.088,27.919 24.739,26.679 C
21.778,25.437 21.126,24.068 22.192,22.603 C 22.787,21.692 23.124,20.787
23.329,19.878 C 23.931,19.843 24.507,19.716 25.047,19.508 C 26.476,21.08
28.884,22.241 29.494,23.708 Z" />
</group>
</group>
</vector>

View File

@ -21,6 +21,10 @@
<group <group
android:checkableBehavior="single" android:checkableBehavior="single"
android:id="@+id/further_options"> android:id="@+id/further_options">
<item
android:id="@+id/donation_link"
android:title="@string/action_donate"
android:icon="@drawable/ic_donate" />
<item <item
android:id="@+id/external_changelog" android:id="@+id/external_changelog"
android:title="@string/changelog_full_title" /> android:title="@string/changelog_full_title" />

View File

@ -6,6 +6,7 @@
<string name="action_settings">Settings</string> <string name="action_settings">Settings</string>
<string name="action_debug">Debug</string> <string name="action_debug">Debug</string>
<string name="action_quit">Quit</string> <string name="action_quit">Quit</string>
<string name="action_donate">Donate</string>
<string name="controlcenter_fetch_activity_data">Synchronize</string> <string name="controlcenter_fetch_activity_data">Synchronize</string>
<string name="controlcenter_start_sleepmonitor">Sleep Monitor (ALPHA)</string> <string name="controlcenter_start_sleepmonitor">Sleep Monitor (ALPHA)</string>
<string name="controlcenter_find_device">Find lost Device…</string> <string name="controlcenter_find_device">Find lost Device…</string>

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<changelog> <changelog>
<release <release version="0.20.1" versioncode="99">
version="0.20.0" <change>Amazfit Bip: Support icons and text body for notifications</change>
versioncode="98"> <change>Mi Band: Fix setting smart alarms</change>
</release>
<release version="0.20.0" versioncode="98">
<change>Inital Amazfit Bip support (WIP)</change> <change>Inital Amazfit Bip support (WIP)</change>
<change>Various theming fixes</change> <change>Various theming fixes</change>
<change>Add workaround for blacklist not properly persisting</change> <change>Add workaround for blacklist not properly persisting</change>
@ -10,7 +12,7 @@
<change>Pebble: Pass booleans from Javascript Appmessage correctly</change> <change>Pebble: Pass booleans from Javascript Appmessage correctly</change>
<change>Pebble: Make local configuration pages work on most recent webview implementation</change> <change>Pebble: Make local configuration pages work on most recent webview implementation</change>
<change>Pebble: Allow to blacklist calendars</change> <change>Pebble: Allow to blacklist calendars</change>
<change>Add greek transliteration support</change> <change>Add Greek and German transliteration support</change>
<change>Various visual improvements to charts</change> <change>Various visual improvements to charts</change>
</release> </release>
<release version="0.19.4" versioncode="97"> <release version="0.19.4" versioncode="97">

View File

@ -1,4 +1,4 @@
Nutze deine Pebble/Mi Band/Hplus, ohne die proprietäre App des Herstellers, ohne ein Benutzerkonto zu erstellen, und ohne irgendwelche Daten an die Server des Herstellers zu senden. Nutze deine Pebble/Mi Band/Amazfit Bip/Hplus, ohne die proprietäre App des Herstellers, ohne ein Benutzerkonto zu erstellen, und ohne irgendwelche Daten an die Server des Herstellers zu senden.
Du kannst Benachrichtigungen an deinem Handgelenk erhalten, sowie (je nach Gerät): Du kannst Benachrichtigungen an deinem Handgelenk erhalten, sowie (je nach Gerät):
- Daten der Gerätesensoren sammeln - Daten der Gerätesensoren sammeln

View File

@ -0,0 +1,2 @@
Nutze deine Pebble/Mi Band/Amazfit Bip/Hplus, ohne die proprietäre App des Herstellers, ohne ein Benutzerkonto zu erstellen, und ohne irgendwelche Daten an die Server des Herstellers zu senden.

View File

@ -0,0 +1,9 @@
* 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

View File

@ -0,0 +1,2 @@
* Amazfit Bip: Support icons and text body for notifications
* Mi Band: Fix setting smart alarms

View File

@ -1,6 +1,7 @@
Use your Pebble/Mi Band/Hplus device 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. Use your Pebble/Mi Band/Amazfit Bip/Hplus device 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.
You can get notifications on your wrist and (depending on the device): You can get notifications on your wrist and (depending on the device):
- collect data from the device sensors - collect data from the device sensors
- control music playing on your android device - control music playing on your android device
- see the weather - see the weather

View File

@ -1 +1 @@
Use your Pebble/Mi Band/Hplus device 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. Use your Pebble/Mi Band/Amazfit Bip/Hplus device 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.

View File

@ -1,4 +1,4 @@
Utilizza il tuo dispositivo Pebble/Mi Band/Hplus senza dipendere dall'applicazione proprietaria del vendor e senza bisogno di creare accounts e trasferire i tuoi dati altrove. Utilizza il tuo dispositivo Pebble/Mi Band/Amazfit Bip/Hplus senza dipendere dall'applicazione proprietaria del vendor e senza bisogno di creare accounts e trasferire i tuoi dati altrove.
Vedi le notifiche direttamente sul tuo polso, e inoltre (a seconda del dispositivo): Vedi le notifiche direttamente sul tuo polso, e inoltre (a seconda del dispositivo):

View File

@ -1 +1 @@
Utilizza il tuo dispositivo Pebble/Mi Band/Hplus senza dipendere dall'applicazione proprietaria del vendor e senza bisogno di creare accounts e trasferire i tuoi dati altrove. Utilizza il tuo dispositivo Pebble/Mi Band/Amazfit Bip/Hplus senza dipendere dall'applicazione proprietaria del vendor e senza bisogno di creare accounts e trasferire i tuoi dati altrove.