diff --git a/FEATURES.md b/FEATURES.md new file mode 100644 index 000000000..2607eb333 --- /dev/null +++ b/FEATURES.md @@ -0,0 +1,34 @@ +## Feature Matrix + +| | Pebble OG | Pebble Time/2 | Mi Band | Mi Band 2 | Amazfit Bip | +|-----------------------------------| ----------|---------------|---------|-----------|-------------| +|Calls Notification | YES | YES | YES | YES | YES | +|Reject Calls | YES | YES | NO | NO | YES | +|Accept Calls | NO(2) | NO(2) | NO | NO | NO(3) | +|Generic Notification | YES | YES | YES | YES | YES | +|Dismiss Notifications on Phone | YES | YES | NO | NO | NO | +|Predefined Replies | YES | YES | NO | NO | NO | +|Voice Replies | N/A | NO(3) | N/A | N/A | N/A | +|Calendar Sync | YES | YES | NO | NO | NO | +|Configure alarms from Gadgetbridge | NO | NO | YES | YES | YES | +|Smart alarms | NO(1) | YES | YES | NO | NO | +|Weather | NO(1) | YES | NO | NO | YES | +|Activity Tracking | NO(1) | YES | YES | YES | YES | +|Sleep Tracking | NO(1) | YES | YES | YES | YES | +|HR Tracking | N/A | YES | YES | YES | YES | +|Realtime Activity Tracking | NO | NO | YES | YES | YES | +|Music Control | YES | YES | NO | NO | NO | +|Watchapp/face Installation | YES | YES | NO | NO | NO | +|Firmware Installaton | YES | YES | YES | YES | YES | +|Taking Screenshots | YES | YES | NO | NO | NO | +|Support Android Companion Apps | YES | YES | NO | NO | NO | + +(1) Possible via 3rd Party Watchapp +(2) Theoretically possible (works on iOS, would need lot of work) +(3) Possible but not implemented yet + + +### Notes about Pebble Firmware >=3.0 + +* Gadgetbridge will keep track of installed watchfaces, but if the Pebble is used with another phone or another app, the information displayed in the app manager can get out of sync since it is impossible to query Firmware >= 3.x for installed apps/watchfaces. + diff --git a/README.md b/README.md index 71b961fe0..c705d0530 100644 --- a/README.md +++ b/README.md @@ -33,31 +33,9 @@ vendor's servers. * Liveview * Vibratissimo (experimental) -## Features (Pebble) +## Features -* Incoming calls notification and display -* Outgoing call display -* Reject calls (optionally with predefined texts) / hangup calls -* SMS notification -* Support for generic notifications -* Support for up to 16 predefined replies for SMS and Android Wear compatible notifications (experimental, tested with Signal and Conversations) -* Dismiss individual notifications, mute or open corresponding app on phone from the action menu (generic notifications) -* Dismiss all notifications from the action menu (SMS and PebbleKit notifications) -* Music playback info (artist, album, track) -* Music control: play/pause, next track, previous track, volume up, volume down -* List and remove installed apps/watchfaces -* Install watchfaces and watchapps (.pbw) -* Install firmware files (.pbz) [READ THE WIKI](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble-Firmware-updates) -* Install language files (.pbl) -* Take and share screenshots from the Pebble's screen -* PebbleKit support for 3rd Party Android Apps (experimental) -* Fetch activity data from Pebble Health -* Build-in support for Misfit and Morpheuz (experimental) -* Configure watchfaces / apps (limited compatibility, experimental) - -## Notes about Firmware >=3.0 (Pebble Time, updated OG) - -* Gadgetbridge will keep track of installed watchfaces, but if the Pebble is used with another phone or another app, the information displayed in the app manager can get out of sync since it is impossible to query Firmware >= 3.x for installed apps/watchfaces. +Please see [FEATURES.md](https://github.com/Freeyourgadget/Gadgetbridge/blob/master/FEATURES.md) ## Getting Started (Pebble) @@ -67,42 +45,6 @@ vendor's servers. For more information read [this wiki article](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble-Getting-Started) -## Features (Mi Band 1x) - -* Discovery and pairing -* Mi Band notifications (LEDs + vibration) for -* Display live activity data (alpha) - * Incoming calls - * SMS received - * K-9 mails received - * Conversations messages - * Generic Android notifications -* Synchronize the time to the Mi Band -* Display firmware version and battery state -* Firmware update -* Heart rate measurement on demand and during sleep -* Synchronize activity data -* Display sleep data (alpha) -* Display sports data (step count) (alpha) -* Display live activity data (alpha) -* Set alarms on the Mi Band - -## Features (Mi Band 2) - -* Discovery and pairing -* Mi Band notifications (Display + vibration) for - * Incoming calls - * SMS received - * K-9 mails received - * Conversations messages - * Generic Android notifications -* Synchronize the time to the Mi Band 2 -* Display firmware version -* Firmware update (beta) -* Heart rate measurement on demand and during sleep -* Synchronize activity data (alpha) -* Set alarms on the Mi Band 2 - ## How to use (Mi Band 1+2) * When starting Gadgetbridge the first time, it will automatically diff --git a/app/build.gradle b/app/build.gradle index c0e7d68c2..10ae381c1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -70,6 +70,7 @@ dependencies { compile 'com.android.support:support-v4:25.3.1' compile 'com.android.support:gridlayout-v7:25.3.1' compile 'com.android.support:design:25.3.1' + compile 'com.android.support:palette-v7:25.3.1' compile 'com.github.tony19:logback-android-classic:1.1.1-6' compile 'org.slf4j:slf4j-api:1.7.7' compile 'com.github.PhilJay:MPAndroidChart:v3.0.2' diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java index 03e3cecdf..198b98ef1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java @@ -126,6 +126,7 @@ public class DebugActivity extends AbstractGBActivity { notificationSpec.sender = testString; notificationSpec.subject = testString; notificationSpec.type = NotificationType.values()[sendTypeSpinner.getSelectedItemPosition()]; + notificationSpec.pebbleColor = notificationSpec.type.color; notificationSpec.id = -1; GBApplication.deviceService().onNotification(notificationSpec); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/AmazfitBipIcon.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/AmazfitBipIcon.java deleted file mode 100644 index 8f952c270..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/AmazfitBipIcon.java +++ /dev/null @@ -1,97 +0,0 @@ -/* 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 . */ - -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; - } -} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/AmazfitBipService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/AmazfitBipService.java index 3d543f69d..fb1c6e3cb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/AmazfitBipService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/AmazfitBipService.java @@ -24,4 +24,6 @@ public class AmazfitBipService { // goes to UUID_CHARACTERISTIC_3_CONFIGURATION, TODO: validate this for Mi Band 2, it maybe triggers more than only GPS version... public static final byte[] COMMAND_REQUEST_GPS_VERSION = new byte[]{0x0e}; + + public static final byte COMMAND_ACTIVITY_DATA_TYPE_DEBUGLOGS = 0x07; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java index 3c4561c4f..723096d61 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java @@ -45,7 +45,7 @@ public class MiBand2Service { public static final UUID UUID_CHARACTERISTIC_5_ACTIVITY_DATA = UUID.fromString("00000005-0000-3512-2118-0009af100700"); public static final UUID UUID_CHARACTERISTIC_6_BATTERY_INFO = UUID.fromString("00000006-0000-3512-2118-0009af100700"); public static final UUID UUID_CHARACTERISTIC_7_REALTIME_STEPS = UUID.fromString("00000007-0000-3512-2118-0009af100700"); - public static final UUID UUID_UNKNOWN_CHARACTERISTIC8 = UUID.fromString("00000008-0000-3512-2118-0009af100700"); + public static final UUID UUID_CHARACTERISTIC_8_USER_SETTINGS = UUID.fromString("00000008-0000-3512-2118-0009af100700"); // service uuid fee1 public static final UUID UUID_CHARACTERISTIC_AUTH = UUID.fromString("00000009-0000-3512-2118-0009af100700"); public static final UUID UUID_CHARACTERISTIC_10_BUTTON = UUID.fromString("00000010-0000-3512-2118-0009af100700"); @@ -104,44 +104,13 @@ public class MiBand2Service { public static final byte AUTH_BYTE = 0x8; // maybe not really activity data, but steps? - public static final byte COMMAND_FETCH_ACTIVITY_DATA = 0x02; + public static final byte COMMAND_FETCH_DATA = 0x02; public static final byte COMMAND_XXXX_ACTIVITY_DATA = 0x03; // maybe delete/drop activity data? public static final byte[] COMMAND_SET_FITNESS_GOAL_START = new byte[] { 0x10, 0x0, 0x0 }; public static final byte[] COMMAND_SET_FITNESS_GOAL_END = new byte[] { 0, 0 }; - public static final byte ICON_CHAT = 0x00; - public static final byte ICON_PENGUIN = 0x01; - public static final byte ICON_CHAT_MI = 0x02; - public static final byte ICON_FB = 0x03; - public static final byte ICON_TWITTER = 0x04; - public static final byte ICON_MIBAND = 0x05; - public static final byte ICON_SNAPCHAT = 0x06; - public static final byte ICON_WHATSAPP = 0x07; - public static final byte ICON_MANTA = 0x08; - public static final byte ICON_XX0 = 0x09; - public static final byte ICON_ALARM = 0x10; - public static final byte ICON_SHATTERED_GLASS = 0x11; - public static final byte ICON_INSTAGRAM = 0x12; - public static final byte ICON_CHAT_GHOST = 0x13; - public static final byte ICON_COW = 0x14; - public static final byte ICON_XX2 = 0x15; - public static final byte ICON_XX3 = 0x16; - public static final byte ICON_XX4 = 0x17; - public static final byte ICON_XX5 = 0x18; - public static final byte ICON_XX6 = 0x19; - public static final byte ICON_EGALE = 0x1a; - public static final byte ICON_CALENDAR = 0x1b; - public static final byte ICON_XX7 = 0x1c; - public static final byte ICON_PHONE_CALL = 0x1d; - public static final byte ICON_CHAT_LINE = 0x1e; - public static final byte ICON_TELEGRAM = 0x1f; - public static final byte ICON_CHAT_TALK = 0x20; - public static final byte ICON_SKYPE = 0x21; - public static final byte ICON_VK = 0x22; - public static final byte ICON_CIRCLES = 0x23; - public static final byte ICON_HANGOUTS = 0x24; - public static final byte ICON_MI = 0x25; + public static final byte COMMAND_SET_USERINFO = 0x4f; public static final byte ICON_HIGH_PRIORITY = 0x7; @@ -209,7 +178,7 @@ public class MiBand2Service { public static final byte COMMAND_ACTIVITY_DATA_START_DATE = 0x01; public static final byte COMMAND_ACTIVITY_DATA_TYPE_ACTIVTY = 0x01; public static final byte COMMAND_ACTIVITY_DATA_TYPE_UNKNOWN_2 = 0x02; - public static final byte COMMAND_ACTIVITY_DATA_XXX_DATE = 0x02; // issued on first connect, followd by COMMAND_XXXX_ACTIVITY_DATA instead of COMMAND_FETCH_ACTIVITY_DATA + public static final byte COMMAND_ACTIVITY_DATA_XXX_DATE = 0x02; // issued on first connect, followd by COMMAND_XXXX_ACTIVITY_DATA instead of COMMAND_FETCH_DATA public static final byte COMMAND_FIRMWARE_INIT = 0x01; // to UUID_CHARACTERISTIC_FIRMWARE, followed by fw file size in bytes public static final byte COMMAND_FIRMWARE_START_DATA = 0x03; // to UUID_CHARACTERISTIC_FIRMWARE diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband2/MiBand2Icon.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband2/MiBand2Icon.java new file mode 100644 index 000000000..41518407b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband2/MiBand2Icon.java @@ -0,0 +1,113 @@ +/* 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 . */ + +package nodomain.freeyourgadget.gadgetbridge.devices.miband2; + + +import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; + +public class MiBand2Icon { + // icons which are unsure which app they are for are suffixed with _NN + public static final byte WECHAT = 0; + public static final byte PENGUIN_1 = 1; + public static final byte MI_CHAT_2 = 2; + public static final byte FACEBOOK = 3; + public static final byte TWITTER = 4; + public static final byte MI_APP_5 = 5; + public static final byte SNAPCHAT = 6; + public static final byte WHATSAPP = 7; + public static final byte RED_WHITE_FIRE_8 = 8; + public static final byte CHINESE_9 = 9; + public static final byte ALARM_CLOCK = 10; + public static final byte APP_11 = 11; + public static final byte INSTAGRAM = 12; + public static final byte CHAT_BLUE_13 = 13; + public static final byte COW_14 = 14; + public static final byte CHINESE_15 = 15; + public static final byte CHINESE_16 = 16; + public static final byte STAR_17 = 17; + public static final byte APP_18 = 18; + public static final byte CHINESE_19 = 19; + public static final byte CHINESE_20 = 20; + public static final byte CALENDAR = 21; + public static final byte FACEBOOK_MESSENGER = 22; + public static final byte VIBER = 23; + public static final byte LINE = 24; + public static final byte TELEGRAM = 25; + public static final byte KAKAOTALK = 26; + public static final byte SKYPE = 27; + public static final byte VKONTAKTE = 28; + public static final byte POKEMONGO = 29; + public static final byte HANGOUTS = 30; + public static final byte MI_31 = 31; + public static final byte CHINESE_32 = 32; + public static final byte CHINESE_33 = 33; + public static final byte EMAIL = 34; + public static final byte WEATHER = 35; + public static final byte HR_WARNING_36 = 36; + + + public static byte mapToIconId(NotificationType type) { + switch (type) { + case UNKNOWN: + return APP_11; + case CONVERSATIONS: + return WECHAT; + case GENERIC_EMAIL: + return EMAIL; + case GENERIC_NAVIGATION: + return APP_11; + case GENERIC_SMS: + return WECHAT; + case GENERIC_CALENDAR: + return CALENDAR; + case FACEBOOK: + return FACEBOOK; + case FACEBOOK_MESSENGER: + return FACEBOOK_MESSENGER; + case GOOGLE_HANGOUTS: + return HANGOUTS; + case INSTAGRAM: + return INSTAGRAM; + case KAKAO_TALK: + return KAKAOTALK; + case LINE: + return LINE; + case RIOT: + return WECHAT; + case SIGNAL: + return CHAT_BLUE_13; + case TWITTER: + return TWITTER; + case SKYPE: + return SKYPE; + case SNAPCHAT: + return SNAPCHAT; + case TELEGRAM: + return TELEGRAM; + case VIBER: + return VIBER; + case WECHAT: + return WECHAT; + case WHATSAPP: + return WHATSAPP; + case GENERIC_ALARM_CLOCK: + return ALARM_CLOCK; + } + return APP_11; + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index 6b77850ff..48ee2ca32 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -28,6 +28,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.media.MediaMetadata; import android.media.session.PlaybackState; import android.os.Bundle; @@ -42,21 +45,26 @@ 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 android.support.v7.graphics.Palette; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; +import java.util.Objects; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor; import nodomain.freeyourgadget.gadgetbridge.model.AppNotificationType; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; +import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil; import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; +import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class NotificationListener extends NotificationListenerService { @@ -192,7 +200,7 @@ public class NotificationListener extends NotificationListenerService { return; } - String source = sbn.getPackageName(); + String source = sbn.getPackageName().toLowerCase(); Notification notification = sbn.getNotification(); NotificationSpec notificationSpec = new NotificationSpec(); notificationSpec.id = (int) sbn.getPostTime(); //FIMXE: a truly unique id would be better @@ -211,6 +219,9 @@ public class NotificationListener extends NotificationListenerService { boolean preferBigText = false; + // Get the app ID that generated this notification. For now only used by pebble color, but may be more useful later. + notificationSpec.sourceAppId = source; + notificationSpec.type = AppNotificationType.getInstance().get(source); if (source.startsWith("com.fsck.k9")) { @@ -221,6 +232,9 @@ public class NotificationListener extends NotificationListenerService { notificationSpec.type = NotificationType.UNKNOWN; } + // Get color + notificationSpec.pebbleColor = getPebbleColorForNotification(notificationSpec); + LOG.info("Processing notification " + notificationSpec.id + " from source " + source + " with flags: " + notification.flags); dissectNotificationTo(notification, notificationSpec, preferBigText); @@ -379,13 +393,9 @@ public class NotificationListener extends NotificationListenerService { return true; } - if (shouldIgnoreSource(sbn.getPackageName())) - return true; + return shouldIgnoreSource(sbn.getPackageName()) || shouldIgnoreNotification( + sbn.getNotification()); - if (shouldIgnoreNotification(sbn.getNotification())) - return true; - - return false; } private boolean shouldIgnoreSource(String source) { @@ -442,12 +452,46 @@ public class NotificationListener extends NotificationListenerService { } } - 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 (notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT; - return false; } + + /** + * Get the notification color that should be used for this Pebble notification. + * + * Note that this method will *not* edit the NotificationSpec passed in. It will only evaluate the PebbleColor. + * + * See Issue #815 on GitHub to see how notification colors are set. + * + * @param notificationSpec The NotificationSpec to read from. + * @return Returns a PebbleColor that best represents this notification. + */ + private byte getPebbleColorForNotification(NotificationSpec notificationSpec) { + String appId = notificationSpec.sourceAppId; + NotificationType existingType = notificationSpec.type; + + // If the notification type is known, return the associated color. + if (existingType != NotificationType.UNKNOWN) { + return existingType.color; + } + + // Otherwise, we go and attempt to find the color from the app icon. + Drawable icon; + try { + icon = getApplicationContext().getPackageManager().getApplicationIcon(appId); + Objects.requireNonNull(icon); + } catch (Exception ex) { + // If we can't get the icon, we go with the default defined above. + LOG.warn("Could not get icon for AppID " + appId, ex); + return PebbleColor.IslamicGreen; + } + + Bitmap bitmapIcon = BitmapUtil.convertDrawableToBitmap(icon); + int iconPrimaryColor = new Palette.Builder(bitmapIcon) + .generate() + .getVibrantColor(Color.parseColor("#aa0000")); + + return PebbleUtils.getPebbleColor(iconPrimaryColor); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java index 578b5098e..f5fea020a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -141,7 +141,8 @@ public class GBDeviceService implements DeviceService { .putExtra(EXTRA_NOTIFICATION_BODY, notificationSpec.body) .putExtra(EXTRA_NOTIFICATION_ID, notificationSpec.id) .putExtra(EXTRA_NOTIFICATION_TYPE, notificationSpec.type) - .putExtra(EXTRA_NOTIFICATION_SOURCENAME, notificationSpec.sourceName); + .putExtra(EXTRA_NOTIFICATION_SOURCENAME, notificationSpec.sourceName) + .putExtra(EXTRA_NOTIFICATION_PEBBLE_COLOR, notificationSpec.pebbleColor); invokeService(intent); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AppNotificationType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AppNotificationType.java index 7e0d7b805..9ea8021f0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AppNotificationType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AppNotificationType.java @@ -53,6 +53,9 @@ public class AppNotificationType extends HashMap { put("com.google.android.apps.inbox", NotificationType.GOOGLE_INBOX); put("com.google.android.calendar", NotificationType.GENERIC_CALENDAR); put("com.google.android.apps.messaging", NotificationType.GOOGLE_MESSENGER); + put("com.google.android.talk", NotificationType.GOOGLE_HANGOUTS); + put("com.google.android.apps.maps", NotificationType.GOOGLE_MAPS); + put("com.google.android.apps.photos", NotificationType.GOOGLE_PHOTOS); // Conversations put("eu.siacs.conversations", NotificationType.CONVERSATIONS); @@ -84,6 +87,57 @@ public class AppNotificationType extends HashMap { // WhatsApp put("com.whatsapp", NotificationType.WHATSAPP); + + // HipChat + put("com.hipchat", NotificationType.HIPCHAT); + + // Skype + put("com.skype.raider", NotificationType.SKYPE); + + // Mailbox + put("com.mailboxapp", NotificationType.MAILBOX); + + // Snapchat + put("com.snapchat.android", NotificationType.SNAPCHAT); + + // WeChat + put("com.tencent.mm", NotificationType.WECHAT); + + // Viber + put("com.viber.voip", NotificationType.VIBER); + + // Instagram + put("com.instagram.android", NotificationType.INSTAGRAM); + + // Kik + put("kik.android", NotificationType.KIK); + + // Line + put("jp.naver.line.android", NotificationType.LINE); + + // BBM + put("com.bbm", NotificationType.BBM); + + // Microsoft Outlook + put("com.microsoft.office.outlook", NotificationType.OUTLOOK); + + // Yahoo Mail + put("com.yahoo.mobile.client.android.mail", NotificationType.YAHOO_MAIL); + + // Kakao Talk + put("com.kakao.talk", NotificationType.KAKAO_TALK); + + // Amazon + put("com.amazon.mshop.android.shopping", NotificationType.AMAZON); + + // LinkedIn + put("com.linkedin.android", NotificationType.LINKEDIN); + + // Slack + put("com.slack", NotificationType.SLACK); + + // Transit + put("com.thetransitapp.droid", NotificationType.TRANSIT); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java index f89ed766c..1c618c3ad 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -73,6 +73,7 @@ public interface DeviceService extends EventHandler { String EXTRA_NOTIFICATION_SUBJECT = "notification_subject"; String EXTRA_NOTIFICATION_TITLE = "notification_title"; String EXTRA_NOTIFICATION_TYPE = "notification_type"; + String EXTRA_NOTIFICATION_PEBBLE_COLOR = "notification_pebble_color"; String EXTRA_FIND_START = "find_start"; String EXTRA_VIBRATION_INTENSITY = "vibration_intensity"; String EXTRA_CALL_COMMAND = "call_command"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java index ce5f3da81..83f31e92b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java @@ -29,4 +29,14 @@ public class NotificationSpec { public NotificationType type; public String sourceName; public String[] cannedReplies; + + /** + * The application that generated the notification. + */ + public String sourceAppId; + + /** + * The color that should be assigned to this notification when displayed on a Pebble + */ + public byte pebbleColor; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java index 228de8650..be716fc76 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java @@ -23,24 +23,47 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID; public enum NotificationType { // TODO: this this pebbleism needs to be moved somewhere else - UNKNOWN(PebbleIconID.NOTIFICATION_GENERIC, PebbleColor.Red), + UNKNOWN(PebbleIconID.NOTIFICATION_GENERIC, PebbleColor.DarkCandyAppleRed), + AMAZON(PebbleIconID.NOTIFICATION_AMAZON, PebbleColor.ChromeYellow), + BBM(PebbleIconID.NOTIFICATION_BLACKBERRY_MESSENGER, PebbleColor.DarkGray), CONVERSATIONS(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.Inchworm), - GENERIC_EMAIL(PebbleIconID.GENERIC_EMAIL, PebbleColor.JaegerGreen), + FACEBOOK(PebbleIconID.NOTIFICATION_FACEBOOK, PebbleColor.CobaltBlue), + FACEBOOK_MESSENGER(PebbleIconID.NOTIFICATION_FACEBOOK_MESSENGER, PebbleColor.BlueMoon), + GENERIC_ALARM_CLOCK(PebbleIconID.ALARM_CLOCK, PebbleColor.Red), + GENERIC_CALENDAR(PebbleIconID.TIMELINE_CALENDAR, PebbleColor.BlueMoon), + GENERIC_EMAIL(PebbleIconID.GENERIC_EMAIL, PebbleColor.Orange), GENERIC_NAVIGATION(PebbleIconID.LOCATION, PebbleColor.Orange), + GENERIC_PHONE(PebbleIconID.DURING_PHONE_CALL, PebbleColor.JaegerGreen), GENERIC_SMS(PebbleIconID.GENERIC_SMS, PebbleColor.VividViolet), - GENERIC_CALENDAR(PebbleIconID.TIMELINE_CALENDAR, PebbleColor.Blue), GMAIL(PebbleIconID.NOTIFICATION_GMAIL, PebbleColor.Red), - GOOGLE_INBOX(PebbleIconID.NOTIFICATION_GOOGLE_INBOX, PebbleColor.Blue), - GOOGLE_MESSENGER(PebbleIconID.NOTIFICATION_GOOGLE_MESSENGER, PebbleColor.Blue), - FACEBOOK(PebbleIconID.NOTIFICATION_FACEBOOK, PebbleColor.Liberty), - FACEBOOK_MESSENGER(PebbleIconID.NOTIFICATION_FACEBOOK_MESSENGER, PebbleColor.VeryLightBlue), + GOOGLE_HANGOUTS(PebbleIconID.NOTIFICATION_GOOGLE_HANGOUTS, PebbleColor.JaegerGreen), + GOOGLE_INBOX(PebbleIconID.NOTIFICATION_GOOGLE_INBOX, PebbleColor.BlueMoon), + GOOGLE_MAPS(PebbleIconID.NOTIFICATION_GOOGLE_MAPS, PebbleColor.BlueMoon), + GOOGLE_MESSENGER(PebbleIconID.NOTIFICATION_GOOGLE_MESSENGER, PebbleColor.VividCerulean), + GOOGLE_PHOTOS(PebbleIconID.NOTIFICATION_GOOGLE_PHOTOS, PebbleColor.BlueMoon), + HIPCHAT(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.CobaltBlue), + INSTAGRAM(PebbleIconID.NOTIFICATION_INSTAGRAM, PebbleColor.CobaltBlue), + KAKAO_TALK(PebbleIconID.NOTIFICATION_KAKAOTALK, PebbleColor.Yellow), + KIK(PebbleIconID.NOTIFICATION_KIK, PebbleColor.IslamicGreen), + LIGHTHOUSE(PebbleIconID.NOTIFICATION_LIGHTHOUSE, PebbleColor.PictonBlue), // ??? - No idea what this is, but it works. + LINE(PebbleIconID.NOTIFICATION_LINE, PebbleColor.IslamicGreen), + LINKEDIN(PebbleIconID.NOTIFICATION_LINKEDIN, PebbleColor.CobaltBlue), + MAILBOX(PebbleIconID.NOTIFICATION_MAILBOX, PebbleColor.VividCerulean), + OUTLOOK(PebbleIconID.NOTIFICATION_OUTLOOK, PebbleColor.BlueMoon), RIOT(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.LavenderIndigo), SIGNAL(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.BlueMoon), + SKYPE(PebbleIconID.NOTIFICATION_SKYPE, PebbleColor.VividCerulean), + SLACK(PebbleIconID.NOTIFICATION_SLACK, PebbleColor.Folly), + SNAPCHAT(PebbleIconID.NOTIFICATION_SNAPCHAT, PebbleColor.Icterine), + TELEGRAM(PebbleIconID.NOTIFICATION_TELEGRAM, PebbleColor.VividCerulean), + TRANSIT(PebbleIconID.LOCATION, PebbleColor.JaegerGreen), TWITTER(PebbleIconID.NOTIFICATION_TWITTER, PebbleColor.BlueMoon), - TELEGRAM(PebbleIconID.NOTIFICATION_TELEGRAM, PebbleColor.PictonBlue), - WHATSAPP(PebbleIconID.NOTIFICATION_WHATSAPP, PebbleColor.MayGreen), - GENERIC_ALARM_CLOCK(PebbleIconID.ALARM_CLOCK, PebbleColor.Red); + VIBER(PebbleIconID.NOTIFICATION_VIBER, PebbleColor.VividViolet), + WECHAT(PebbleIconID.NOTIFICATION_WECHAT, PebbleColor.KellyGreen), + WHATSAPP(PebbleIconID.NOTIFICATION_WHATSAPP, PebbleColor.IslamicGreen), + YAHOO_MAIL(PebbleIconID.NOTIFICATION_YAHOO_MAIL, PebbleColor.Indigo); + // Note: if you add any more constants, update all clients as well public final int icon; @@ -69,6 +92,9 @@ public enum NotificationType { return getFixedValue(); case FACEBOOK: case TWITTER: + case SNAPCHAT: + case INSTAGRAM: + case LINKEDIN: return "generic_social"; case CONVERSATIONS: case FACEBOOK_MESSENGER: @@ -77,10 +103,23 @@ public enum NotificationType { case TELEGRAM: case WHATSAPP: case GOOGLE_MESSENGER: + case GOOGLE_HANGOUTS: + case HIPCHAT: + case SKYPE: + case WECHAT: + case KIK: + case KAKAO_TALK: + case SLACK: return "generic_chat"; case GMAIL: case GOOGLE_INBOX: + case MAILBOX: + case OUTLOOK: + case YAHOO_MAIL: return "generic_email"; + case LINE: + case VIBER: + return "generic_phone"; case UNKNOWN: default: return "generic"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index 73e89821e..78e2dcb1c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -138,6 +138,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUS import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_BODY; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_FLAGS; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_ID; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_PEBBLE_COLOR; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_PHONENUMBER; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SENDER; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SOURCENAME; @@ -343,6 +344,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere notificationSpec.body = intent.getStringExtra(EXTRA_NOTIFICATION_BODY); notificationSpec.sourceName = intent.getStringExtra(EXTRA_NOTIFICATION_SOURCENAME); notificationSpec.type = (NotificationType) intent.getSerializableExtra(EXTRA_NOTIFICATION_TYPE); + notificationSpec.pebbleColor = (byte) intent.getSerializableExtra(EXTRA_NOTIFICATION_PEBBLE_COLOR); notificationSpec.id = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1); notificationSpec.flags = intent.getIntExtra(EXTRA_NOTIFICATION_FLAGS, 0); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/NewAlert.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/NewAlert.java index 3aa43dba5..f61ebc063 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/NewAlert.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/alertnotification/NewAlert.java @@ -49,7 +49,7 @@ public class NewAlert { private final AlertCategory category; private final int numAlerts; private final String message; - private int customIcon = -1; + private byte customIcon = -1; public NewAlert(AlertCategory category, int /*uint8*/ numAlerts, String /*utf8s*/ message) { this.category = category; @@ -57,7 +57,7 @@ public class NewAlert { this.message = message; } - public NewAlert(AlertCategory category, int /*uint8*/ numAlerts, String /*utf8s*/ message, int customIcon) { + public NewAlert(AlertCategory category, int /*uint8*/ numAlerts, String /*utf8s*/ message, byte customIcon) { this.category = category; this.numAlerts = numAlerts; this.message = message; @@ -76,7 +76,7 @@ public class NewAlert { return message; } - public int getCustomIcon() { + public byte getCustomIcon() { return customIcon; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipEvent.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipEvent.java new file mode 100644 index 000000000..e4b9dc8c2 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipEvent.java @@ -0,0 +1,31 @@ +/* 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip; + + +public class AmazfitBipEvent { + public static final byte FELL_ASLEEP = 0x01; + public static final byte WOKE_UP = 0x02; + public static final byte STEPSGOAL_REACHED = 0x03; + public static final byte BUTTON_PRESSED = 0x04; + public static final byte START_NONWEAR = 0x06; + public static final byte CALL_REJECT = 0x07; + public static final byte CALL_ACCEPT = 0x09; + public static final byte ALARM_TOGGLED = 0x0a; + public static final byte BUTTON_PRESSED_LONG = 0x0b; + public static final byte TICK_30MIN = 0x0e; // unsure +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipFirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipFirmwareInfo.java index aec1c2bee..5874d0217 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipFirmwareInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipFirmwareInfo.java @@ -41,15 +41,26 @@ public class AmazfitBipFirmwareInfo extends Mi2FirmwareInfo { 0x48, 0x4d, 0x52, 0x45, 0x53 }; + private static final byte[] GPS_ALMANAC_HEADER = new byte[]{ // probably wrong + (byte) 0xa0, (byte) 0x80, 0x08, 0x00, (byte) 0x8b, 0x07 + }; + + private static final byte[] GPS_CEP_HEADER = new byte[]{ // probably wrong + 0x2a, 0x12, (byte) 0xa0, 0x02 + }; static { // firmware crcToVersion.put(25257, "0.0.8.74"); crcToVersion.put(57724, "0.0.8.88"); + crcToVersion.put(27668, "0.0.8.96"); + crcToVersion.put(60173, "0.0.8.97"); + crcToVersion.put(3462, "0.0.8.98"); // resources crcToVersion.put(12586, "RES 0.0.8.74"); crcToVersion.put(34068, "RES 0.0.8.88"); + crcToVersion.put(59839, "RES 0.0.8.96-98"); // gps crcToVersion.put(61520, "GPS 9367,8f79a91,0,0,"); @@ -67,6 +78,12 @@ public class AmazfitBipFirmwareInfo extends Mi2FirmwareInfo { if (ArrayUtils.startsWith(bytes, GPS_HEADER)) { return FirmwareType.GPS; } + if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) { + return FirmwareType.GPS_ALMANAC; + } + if (ArrayUtils.startsWith(bytes, GPS_CEP_HEADER)) { + return FirmwareType.GPS_CEP; + } 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; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipSupport.java index af877879d..2498986bd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipSupport.java @@ -30,12 +30,11 @@ import java.nio.ByteOrder; import java.util.SimpleTimeZone; import java.util.UUID; -import nodomain.freeyourgadget.gadgetbridge.Logging; 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.devices.miband.MiBand2Service; +import nodomain.freeyourgadget.gadgetbridge.devices.miband2.MiBand2Icon; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; @@ -44,6 +43,7 @@ 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.AmazfitBipFetchLogsOperation; 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; @@ -82,7 +82,7 @@ public class AmazfitBipSupport extends MiBand2Support { AlertNotificationProfile profile = new AlertNotificationProfile(this); profile.setMaxLength(230); - int customIconId = AmazfitBipIcon.mapToIconId(notificationSpec.type); + byte customIconId = MiBand2Icon.mapToIconId(notificationSpec.type); AlertCategory alertCategory = AlertCategory.CustomMiBand2; @@ -118,15 +118,42 @@ public class AmazfitBipSupport extends MiBand2Support { } GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl(); - if (value[0] == 0x07) { - callCmd.event = GBDeviceEventCallControl.Event.REJECT; - } else if (value[0] == 0x09) { - callCmd.event = GBDeviceEventCallControl.Event.ACCEPT; - } else { - LOG.info("Unhandled button press: " + Logging.formatBytes(value)); - return; + switch (value[0]) { + case AmazfitBipEvent.CALL_REJECT: + callCmd.event = GBDeviceEventCallControl.Event.REJECT; + evaluateGBDeviceEvent(callCmd); + break; + case AmazfitBipEvent.CALL_ACCEPT: + callCmd.event = GBDeviceEventCallControl.Event.ACCEPT; + evaluateGBDeviceEvent(callCmd); + break; + case AmazfitBipEvent.BUTTON_PRESSED: + LOG.info("button pressed"); + break; + case AmazfitBipEvent.BUTTON_PRESSED_LONG: + LOG.info("button long-pressed "); + break; + case AmazfitBipEvent.START_NONWEAR: + LOG.info("non-wear start detected"); + break; + case AmazfitBipEvent.ALARM_TOGGLED: + LOG.info("An alarm was toggled"); // TODO: sync alarms watch -> GB + break; + case AmazfitBipEvent.FELL_ASLEEP: + LOG.info("Fell asleep"); + break; + case AmazfitBipEvent.WOKE_UP: + LOG.info("Woke up"); + break; + case AmazfitBipEvent.STEPSGOAL_REACHED: + LOG.info("Steps goal reached"); + break; + case AmazfitBipEvent.TICK_30MIN: + LOG.info("Tick 30 min (?)"); + break; + default: + LOG.warn("unhandled event " + value[0]); } - evaluateGBDeviceEvent(callCmd); } @Override @@ -198,6 +225,15 @@ public class AmazfitBipSupport extends MiBand2Support { } } + @Override + public void onTestNewFunction() { + try { + new AmazfitBipFetchLogsOperation(this).perform(); + } catch (IOException ex) { + LOG.error("Unable to fetch logs", ex); + } + } + @Override public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/operations/AmazfitBipFetchLogsOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/operations/AmazfitBipFetchLogsOperation.java new file mode 100644 index 000000000..429dd3945 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/operations/AmazfitBipFetchLogsOperation.java @@ -0,0 +1,131 @@ +/* Copyright (C) 2016-2017 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.operations; + +import android.support.annotation.NonNull; +import android.support.v4.util.TimeUtils; +import android.widget.Toast; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipService; +import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service; +import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction; +import nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.AmazfitBipSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.AbstractFetchOperation; +import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +public class AmazfitBipFetchLogsOperation extends AbstractFetchOperation { + private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipFetchLogsOperation.class); + + private FileOutputStream logOutputStream; + + public AmazfitBipFetchLogsOperation(AmazfitBipSupport support) { + super(support); + } + + @Override + protected void startFetching(TransactionBuilder builder) { + File dir; + try { + dir = FileUtils.getExternalFilesDir(); + } catch (IOException e) { + return; + } + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-hhmmss", Locale.US); + String filename = "amazfitbip_" + dateFormat.format(new Date()) + ".log"; + + File outputFile = new File(dir, filename ); + try { + logOutputStream = new FileOutputStream(outputFile); + } catch (IOException e) { + LOG.warn("could not create file " + outputFile, e); + return; + } + + GregorianCalendar sinceWhen = BLETypeConversions.createCalendar(); + sinceWhen.add(Calendar.DAY_OF_MONTH, -10); + builder.write(characteristicFetch, BLETypeConversions.join(new byte[]{ + MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE, + AmazfitBipService.COMMAND_ACTIVITY_DATA_TYPE_DEBUGLOGS}, + getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES))); + builder.add(new WaitAction(1000)); // TODO: actually wait for the success-reply + builder.notify(characteristicActivityData, true); + builder.write(characteristicFetch, new byte[]{MiBand2Service.COMMAND_FETCH_DATA}); + } + + @Override + protected String getLastSyncTimeKey() { + return null; + } + + @Override + protected void handleActivityFetchFinish() { + LOG.info("Fetching log data has finished"); + try { + logOutputStream.close(); + logOutputStream = null; + } catch (IOException e) { + LOG.warn("could not close output stream", e); + return; + } + super.handleActivityFetchFinish(); + } + + @Override + protected void handleActivityNotif(byte[] value) { + if (!isOperationRunning()) { + LOG.error("ignoring notification because operation is not running. Data length: " + value.length); + getSupport().logMessageContent(value); + return; + } + + if ((byte) (lastPacketCounter + 1) == value[0]) { + lastPacketCounter++; + bufferActivityData(value); + } else { + GB.toast("Error fetching activity data, invalid package counter: " + value[0], Toast.LENGTH_LONG, GB.ERROR); + handleActivityFetchFinish(); + } + } + + @Override + protected void bufferActivityData(@NonNull byte[] value) { + try { + logOutputStream.write(Arrays.copyOfRange(value, 1, value.length)); + } catch (IOException e) { + LOG.warn("could not write to output stream", e); + handleActivityFetchFinish(); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/FirmwareType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/FirmwareType.java index b83131fa1..34b09f286 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/FirmwareType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/FirmwareType.java @@ -19,8 +19,11 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2; public enum FirmwareType { FIRMWARE((byte) 0), FONT((byte) 1), + // Amazfit Bip only from here on RES((byte) 2), GPS((byte) 3), + GPS_CEP((byte) 4), + GPS_ALMANAC((byte)5), INVALID(Byte.MIN_VALUE); private final byte value; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2TextNotificationStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2TextNotificationStrategy.java index 8b6d11f65..3affdd36d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2TextNotificationStrategy.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2TextNotificationStrategy.java @@ -21,6 +21,7 @@ import android.support.annotation.NonNull; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service; import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile; +import nodomain.freeyourgadget.gadgetbridge.devices.miband2.MiBand2Icon; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic; @@ -68,9 +69,9 @@ public class Mi2TextNotificationStrategy extends Mi2NotificationStrategy { case Email: return new byte[] { BLETypeConversions.fromUint8(AlertCategory.Email.getId()), BLETypeConversions.fromUint8(numAlerts)}; case InstantMessage: - return new byte[] { BLETypeConversions.fromUint8(AlertCategory.CustomMiBand2.getId()), BLETypeConversions.fromUint8(numAlerts), MiBand2Service.ICON_CHAT}; + return new byte[] { BLETypeConversions.fromUint8(AlertCategory.CustomMiBand2.getId()), BLETypeConversions.fromUint8(numAlerts), MiBand2Icon.WECHAT}; case News: - return new byte[] { BLETypeConversions.fromUint8(AlertCategory.CustomMiBand2.getId()), BLETypeConversions.fromUint8(numAlerts), MiBand2Service.ICON_PENGUIN}; + return new byte[] { BLETypeConversions.fromUint8(AlertCategory.CustomMiBand2.getId()), BLETypeConversions.fromUint8(numAlerts), MiBand2Icon.PENGUIN_1}; } } return new byte[] { BLETypeConversions.fromUint8(AlertCategory.SMS.getId()), BLETypeConversions.fromUint8(numAlerts)}; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java index e3c0d883a..773681f79 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java @@ -350,7 +350,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { private MiBand2Support setFitnessGoal(TransactionBuilder transaction) { LOG.info("Attempting to set Fitness Goal..."); - BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC8); + BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_8_USER_SETTINGS); if (characteristic != null) { int fitnessGoal = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, 10000); byte[] bytes = ArrayUtils.addAll( @@ -365,6 +365,68 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { return this; } + /** + * Part of device initialization process. Do not call manually. + * + * @param transaction + * @return + */ + + private MiBand2Support setUserInfo(TransactionBuilder transaction) { + BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_8_USER_SETTINGS); + if (characteristic == null) { + return this; + } + + LOG.info("Attempting to set user info..."); + Prefs prefs = GBApplication.getPrefs(); + String alias = prefs.getString(MiBandConst.PREF_USER_ALIAS, null); + ActivityUser activityUser = new ActivityUser(); + int height = activityUser.getHeightCm(); + int weight = activityUser.getWeightKg(); + int birth_year = activityUser.getYearOfBirth(); + byte birth_month = 7; // not in user attributes + byte birth_day = 1; // not in user attributes + + if (alias == null || weight == 0 || height == 0 || birth_year == 0) { + LOG.warn("Unable to set user info, make sure it is set up"); + return this; + } + + byte sex = 2; // other + switch (activityUser.getGender()) { + case ActivityUser.GENDER_MALE: + sex = 0; + break; + case ActivityUser.GENDER_FEMALE: + sex = 1; + } + int userid = alias.hashCode(); // hash from alias like mi1 + + // FIXME: Do encoding like in PebbleProtocol, this is ugly + byte bytes[] = new byte[]{ + MiBand2Service.COMMAND_SET_USERINFO, + 0, + 0, + (byte) (birth_year & 0xff), + (byte) ((birth_year >> 8) & 0xff), + birth_month, + birth_day, + sex, + (byte) (height & 0xff), + (byte) ((height >> 8) & 0xff), + (byte) ((weight * 200) & 0xff), + (byte) (((weight * 200) >> 8) & 0xff), + (byte) (userid & 0xff), + (byte) ((userid >> 8) & 0xff), + (byte) ((userid >> 16) & 0xff), + (byte) ((userid >> 24) & 0xff) + }; + + transaction.write(characteristic, bytes); + return this; + } + /** * Part of device initialization process. Do not call manually. * @@ -373,7 +435,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { */ private MiBand2Support setWearLocation(TransactionBuilder builder) { LOG.info("Attempting to set wear location..."); - BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_UNKNOWN_CHARACTERISTIC8); + BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_8_USER_SETTINGS); if (characteristic != null) { builder.notify(characteristic, true); int location = MiBandCoordinator.getWearLocation(getDevice().getAddress()); @@ -625,13 +687,18 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { public void onReboot() { try { TransactionBuilder builder = performInitialized("Reboot"); - builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_FIRMWARE), new byte[] { MiBand2Service.COMMAND_FIRMWARE_REBOOT}); + sendReboot(builder); builder.queue(getQueue()); } catch (IOException ex) { LOG.error("Unable to reboot MI", ex); } } + public MiBand2Support sendReboot(TransactionBuilder builder) { + builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_FIRMWARE), new byte[] { MiBand2Service.COMMAND_FIRMWARE_REBOOT}); + return this; + } + @Override public void onHeartRateTest() { try { @@ -1395,6 +1462,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { LOG.info("phase3Initialize..."); setDateDisplay(builder); setTimeFormat(builder); + setUserInfo(builder); setWearLocation(builder); setFitnessGoal(builder); setDisplayItems(builder); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/AbstractFetchOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/AbstractFetchOperation.java index 2c95d72f3..8029de8c4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/AbstractFetchOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/AbstractFetchOperation.java @@ -132,10 +132,6 @@ public abstract class AbstractFetchOperation extends AbstractMiBand2Operation { */ protected abstract void handleActivityNotif(byte[] value); - /** - * Creates samples from the given 17-length array - * @param value - */ protected abstract void bufferActivityData(byte[] value); protected void handleActivityMetadata(byte[] value) { @@ -146,7 +142,7 @@ public abstract class AbstractFetchOperation extends AbstractMiBand2Operation { // the 4th - 7th bytes probably somehow represent the number of bytes/packets to expect // last 8 bytes are the start date - Calendar startTimestamp = getSupport().fromTimeBytes(org.apache.commons.lang3.ArrayUtils.subarray(value, 7, value.length)); + Calendar startTimestamp = getSupport().fromTimeBytes(Arrays.copyOfRange(value, 7, value.length)); setStartTimestamp(startTimestamp); GB.toast(getContext().getString(R.string.FetchActivityOperation_about_to_transfer_since, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java index 36999acec..9eda8886b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java @@ -71,7 +71,7 @@ public class FetchActivityOperation extends AbstractFetchOperation { builder.write(characteristicFetch, BLETypeConversions.join(new byte[] { MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE, MiBand2Service.COMMAND_ACTIVITY_DATA_TYPE_ACTIVTY }, getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES))); builder.add(new WaitAction(1000)); // TODO: actually wait for the success-reply builder.notify(characteristicActivityData, true); - builder.write(characteristicFetch, new byte[] { MiBand2Service.COMMAND_FETCH_ACTIVITY_DATA }); + builder.write(characteristicFetch, new byte[] { MiBand2Service.COMMAND_FETCH_DATA}); } protected void handleActivityFetchFinish() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchSportsSummaryOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchSportsSummaryOperation.java new file mode 100644 index 000000000..f4d92a007 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchSportsSummaryOperation.java @@ -0,0 +1,152 @@ +/* Copyright (C) 2016-2017 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.widget.Toast; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.concurrent.TimeUnit; + +import nodomain.freeyourgadget.gadgetbridge.Logging; +import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.AmazfitBipService; +import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service; +import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +/** + * An operation that fetches activity data. For every fetch, a new operation must + * be created, i.e. an operation may not be reused for multiple fetches. + */ +public class FetchSportsSummaryOperation extends AbstractFetchOperation { + private static final Logger LOG = LoggerFactory.getLogger(FetchSportsSummaryOperation.class); + +// private List samples = new ArrayList<>(60*24); // 1day per default + + private byte lastPacketCounter; + + public FetchSportsSummaryOperation(MiBand2Support support) { + super(support); + } + + @Override + protected void startFetching(TransactionBuilder builder) { + GregorianCalendar sinceWhen = getLastSuccessfulSyncTime(); +// builder.write(characteristicFetch, BLETypeConversions.join(new byte[] { +// MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE, +// AmazfitBipService.COMMAND_ACTIVITY_DATA_TYPE_SPORTS_SUMMARIES}, +// getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES))); +// builder.add(new WaitAction(1000)); // TODO: actually wait for the success-reply +// builder.notify(characteristicActivityData, true); +// builder.write(characteristicFetch, new byte[] { MiBand2Service.COMMAND_FETCH_DATA }); + } + + @Override + protected void handleActivityFetchFinish() { + 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); +// } +// } + + super.handleActivityFetchFinish(); + } + + @Override + public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + LOG.warn("characteristic read: " + characteristic.getUuid() + ": " + Logging.formatBytes(characteristic.getValue())); + return super.onCharacteristicRead(gatt, characteristic, status); + } + + /** + * Method to handle the incoming activity data. + * There are two kind of messages we currently know: + * - the first one is 11 bytes long and contains metadata (how many bytes to expect, when the data starts, etc.) + * - the second one is 20 bytes long and contains the actual activity data + *

+ * The first message type is parsed by this method, for every other length of the value param, bufferActivityData is called. + * + * @param value + */ + @Override + protected void handleActivityNotif(byte[] value) { + LOG.warn("sports data: " + Logging.formatBytes(value)); + + if (!isOperationRunning()) { + LOG.error("ignoring activity data notification because operation is not running. Data length: " + value.length); + getSupport().logMessageContent(value); + return; + } + + if ((value.length % 4) == 1) { + if ((byte) (lastPacketCounter + 1) == value[0] ) { + lastPacketCounter++; + bufferActivityData(value); + } else { + GB.toast("Error fetching activity data, invalid package counter: " + value[0], Toast.LENGTH_LONG, GB.ERROR); + handleActivityFetchFinish(); + return; + } + } else { + GB.toast("Error fetching activity data, unexpected package length: " + value.length, Toast.LENGTH_LONG, GB.ERROR); + LOG.warn("Unexpected activity data: " + Logging.formatBytes(value)); + } + } + + /** + * Creates samples from the given 17-length array + * @param value + */ + @Override + protected void bufferActivityData(byte[] value) { + // TODO: implement +// int len = value.length; +// +// if (len % 4 != 1) { +// throw new AssertionError("Unexpected activity array size: " + len); +// } +// +// for (int i = 1; i < len; i+=4) { +// } + } + + @Override + protected String getLastSyncTimeKey() { + return getDevice().getAddress() + "_" + "lastSportsSyncTimeMillis"; + } + + + protected GregorianCalendar getLastSuccessfulSyncTime() { + // FIXME: remove this! + GregorianCalendar calendar = BLETypeConversions.createCalendar(); + calendar.add(Calendar.DAY_OF_MONTH, -1); + return calendar; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/UpdateFirmwareOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/UpdateFirmwareOperation.java index b1d2e4bf1..742bb1749 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/UpdateFirmwareOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/UpdateFirmwareOperation.java @@ -97,6 +97,7 @@ public class UpdateFirmwareOperation extends AbstractMiBand2Operation { UUID characteristicUUID = characteristic.getUuid(); if (fwCControlChar.getUuid().equals(characteristicUUID)) { handleNotificationNotif(characteristic.getValue()); + return true; // don't let anyone else handle it } else { super.onCharacteristicChanged(gatt, characteristic); } @@ -134,7 +135,9 @@ public class UpdateFirmwareOperation extends AbstractMiBand2Operation { } case MiBand2Service.COMMAND_FIRMWARE_CHECKSUM: { if (getFirmwareInfo().getFirmwareType() == FirmwareType.FIRMWARE) { - getSupport().onReboot(); + TransactionBuilder builder = performInitialized("reboot"); + getSupport().sendReboot(builder); + builder.queue(getQueue()); } else { GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext()); done(); @@ -142,6 +145,7 @@ public class UpdateFirmwareOperation extends AbstractMiBand2Operation { break; } case MiBand2Service.COMMAND_FIRMWARE_REBOOT: { + LOG.info("Reboot command successfully sent."); GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext()); // getSupport().onReboot(); done(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java index cc9cc995c..f1e6ddba4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java @@ -498,10 +498,13 @@ public class PebbleProtocol extends GBDeviceProtocol { if (mFwMajor >= 3) { // 3.x notification - return encodeBlobdbNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle, notificationSpec.type, notificationSpec.cannedReplies); + return encodeBlobdbNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, + notificationSpec.sourceName, hasHandle, notificationSpec.type, notificationSpec.pebbleColor, + notificationSpec.cannedReplies); } else if (mForceProtocol || notificationSpec.type != NotificationType.GENERIC_EMAIL) { // 2.x notification - return encodeExtensibleNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle, notificationSpec.cannedReplies); + return encodeExtensibleNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, + notificationSpec.sourceName, hasHandle, notificationSpec.cannedReplies); } else { // 1.x notification on FW 2.X String[] parts = {title, notificationSpec.body, ts.toString(), subtitle}; @@ -922,7 +925,9 @@ public class PebbleProtocol extends GBDeviceProtocol { return encodeBlobdb(uuid, BLOBDB_INSERT, BLOBDB_PIN, buf.array()); } - private byte[] encodeBlobdbNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, NotificationType notificationType, String[] cannedReplies) { + private byte[] encodeBlobdbNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, + boolean hasHandle, NotificationType notificationType, byte backgroundColor, + String[] cannedReplies) { final short NOTIFICATION_PIN_LENGTH = 46; final short ACTION_LENGTH_MIN = 10; @@ -933,7 +938,6 @@ public class PebbleProtocol extends GBDeviceProtocol { } int icon_id = notificationType.icon; - byte color_id = notificationType.color; // Calculate length first byte actions_count; @@ -1023,7 +1027,7 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.put((byte) 28); // background_color buf.putShort((short) 1); // length of int - buf.put(color_id); + buf.put(backgroundColor); // dismiss action buf.put(dismiss_action_id); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBMusicControlReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBMusicControlReceiver.java index e008a590e..772029225 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBMusicControlReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBMusicControlReceiver.java @@ -76,22 +76,7 @@ public class GBMusicControlReceiver extends BroadcastReceiver { } if (keyCode != -1) { - Prefs prefs = GBApplication.getPrefs(); - String audioPlayer = prefs.getString("audio_player", "default"); - - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - MediaSessionManager mediaSessionManager = - (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE); - - List controllers = mediaSessionManager.getActiveSessions( - new ComponentName(context, NotificationListener.class)); - try { - MediaController controller = controllers.get(0); - audioPlayer = controller.getPackageName(); - } catch (IndexOutOfBoundsException e) { - System.err.println("IndexOutOfBoundsException: " + e.getMessage()); - } - } + String audioPlayer = getAudioPlayer(context); LOG.debug("keypress: " + musicCmd.toString() + " sent to: " + audioPlayer); @@ -114,4 +99,23 @@ public class GBMusicControlReceiver extends BroadcastReceiver { context.sendOrderedBroadcast(upIntent, null); } } + + private String getAudioPlayer(Context context) { + Prefs prefs = GBApplication.getPrefs(); + String audioPlayer = prefs.getString("audio_player", "default"); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + MediaSessionManager mediaSessionManager = + (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE); + + List controllers = mediaSessionManager.getActiveSessions( + new ComponentName(context, NotificationListener.class)); + try { + MediaController controller = controllers.get(0); + audioPlayer = controller.getPackageName(); + } catch (IndexOutOfBoundsException e) { + LOG.error("IndexOutOfBoundsException: " + e.getMessage()); + } + } + return audioPlayer; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BitmapUtil.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BitmapUtil.java new file mode 100644 index 000000000..681648ece --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BitmapUtil.java @@ -0,0 +1,49 @@ +/* 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 . */ + +package nodomain.freeyourgadget.gadgetbridge.util; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; + +public class BitmapUtil { + + /** + * Get a Bitmap from any given Drawable. + * + * Note that this code will fail if the drawable is 0x0. + * + * @param drawable A Drawable to convert. + * @return A Bitmap representing the drawable. + */ + public static Bitmap convertDrawableToBitmap(Drawable drawable) { + // If whoever made this drawable decided to be nice to us... + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + } + + Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + + return bitmap; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PebbleUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PebbleUtils.java index 70c13e45a..c25704a41 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PebbleUtils.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/PebbleUtils.java @@ -16,6 +16,8 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.util; +import android.graphics.Color; + public class PebbleUtils { public static String getPlatformName(String hwRev) { String platformName; @@ -63,4 +65,29 @@ public class PebbleUtils { String platformName = getPlatformName(hwRev); return !"aplite".equals(platformName); } + + /** + * Get the closest Pebble-compatible color from the associated Android Color Integer. + * @param color An Android Color Integer to convert + * @return A byte representing the closest Pebble color. + */ + public static byte getPebbleColor(int color) { + // 85 here is determined by dividing 255 by 3, or reducing an 8-bit color to a 2-bit color. (2^3 = 8) + + int colorRed = ((color >> 16) & 0xFF) / 85; + int colorGreen = ((color >> 8) & 0xFF) / 85; + int colorBlue = (color & 0xFF) / 85; + + // Bit shifting, woo! + return (byte) ((0b11 << 6) | ((colorRed & 0b11) << 4) | ((colorGreen & 0b11) << 2) | (colorBlue & 0b11)); + } + + /** + * Get the closest Pebble-compatible color from the associated Hex string. + * @param colorHex A Hex-formatted string (#FFDD00) to convert. + * @return A byte representing the closest Pebble color. + */ + public static byte getPebbleColor(String colorHex) { + return getPebbleColor(Color.parseColor(colorHex)); + } } diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml new file mode 100644 index 000000000..8272674ef --- /dev/null +++ b/app/src/main/res/values-fi/strings.xml @@ -0,0 +1,41 @@ + +Asetukset + Synkronisoi + Löydä kadonnut laite + Gadgetbridge + + Gadgetbridge + Sulje + Lahjoita + Ota ruutukaappaus + Katkaise yhteys + Poista laite + Poista %1$s + Tämä toiminto poistaa laitteen ja kaikki siihen liittyvät tiedot! + Avaa navigointivetolaatikko + Sulje navigointivetolaatikko + Paina korttia pitkään katkaistaksesi yhteyden + Yhteyttä katkaistaan + Yhdistetään + Otataan ruutukaappausta laitteesta + + + Sovellusten hallinta + Välimuistissa olevat sovellukset + Asennetut sovellukset + Asennetut kellotaulut + Poista + Poista laitteesta ja välimuistista + Asenna uudelleen + Etsi Pebble Appstoresta + Aktivoi + Poista käytöstä + Aktivoi sykemittari + Poista sykemittari käytöstä + Aktivoi järjestelmän sääsovellus + Ota järjestelmän sääsovellus pois käytöstä + Asenna sääilmoitussovellus + Määritä + Siirrä ylös + + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index cf284f61d..81ff1b2b2 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -186,7 +186,7 @@ Déjà lié avec %1$s (%2$s), connexion… Aucune adresse MAC fournie, ne peut être appairé. Paramètres spécifiques à l\'appareil - Paramètres Mi Band + Paramètres Mi Band / Bip Homme Femme Autre @@ -383,7 +383,7 @@ NOTE: la base de données sera bien évidement plus grande ! Assurez vous que ce thème soit activé dans l\'application de notification de la météo pour recevoir les informations sur votre Pebble.\n\nAucune configuration n\'est requise.\n\nVous pouvez activer l\'application météo système de votre Pebble depuis la configuration de l\'application.\n\nLes watchfaces supportées afficheront la météo automatiquement. Activer le jumelage Bluetooth Désactivez ceci si vous avez des problèmes de connexion - Mesure + Métrique Impériale 24H AM/PM diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml new file mode 100644 index 000000000..a9fff27d2 --- /dev/null +++ b/app/src/main/res/values-gl/strings.xml @@ -0,0 +1,490 @@ + +Gadgetbridge + + Gadgetbridge + Axustes + Depuración + Saír + Doar + Sincronizar + Monitor de sono (Alpha) + Atopar dispositivo perdido + Capturar pantalla + Desconectar + Borrar dispositivo + Borrar %1$s + Borrarás o dispositivo e todos os datos asociados! + Abrir menú de navegación + Pechar menú de navegación + Manter pulsado o icono para desconectar + Desconectando + Conectando + Capturando pantalla do dispositivo + + + Depurar + + Xestor de App + Apps en caché + Apps instaladas + Visores instalados + Borrar + Borrar e retirar da caché + Reinstalar + Buscar na Appstore de Pebble + Activar + Desactivar + Activar Monitor de Ritmo Cardíaco + Desactivar Monitor de Ritmo Cardíaco + Activar app de clima do sistema + Desactivar app de clima do sistema + Instalar a app de notificacións do clima + Configurar + Mover para arriba + + Bloqueo de notificacións + + Calendarios bloqueados + + Instalador de FW/App + Está a piques de instalar o firmware %s no lugar do actual na súa Mi Band. + Está a piques de instalar os firmwares %1$s e %2$s no lugar dos que están actualmente na súa Mi Band. + Este firmware foi probado e é compatíbel co Gadgetbridge. + Este firmware non foi probado e pode non ser compatíbel co Gadgetbridge. +\n +\nÉ preferíbel que NON o instale na súa Mi Band! + Se aínda así quere proceder e todo continúa a funcionar normalmente, por favor avise aos programadores do Gadgetbridge para permitir o firmware versión: %s + + Axustes + + Axustes xerais + Conectar ao dispositivo ao activar o Bluetooth + Iniciar automaticamente + Reconectar automaticamente + Reprodutor de audio preferido + Predefinido + + Data e Hora + Sincronizar hora + Sincronizar hora ao conectar ou ao cambiar o fuso horario no android + + Tema + Claro + Escuro + + Idioma + + Ocultar a notificación de Gadgetbridge + Amosar icono na barra de estado e notificación na pantalla de bloqueo + Ocultar icono na barra de estado e notificación na pantalla de bloqueo + + Notificacións + Repeticións + Chamadas telefónicas + SMS + Mensaxes de Pebble + Soporte para aplicacións que envían notificacións a Pebble através de PebbleKit. + Soporte para notificacións xenéricas + ... tamén coa pantalla acesa + Non Molestar + Parar notificacións indesexadas baseándose no modo Non Molestar + Transliteración + Activar isto se o dispositivo non ten soporte para o seu idioma + + Sempre + Cando a pantalla está apagada + Nunca + + Privacidade + Modo de privacidade de chamada + Amosar nome e número + Ocultar nome pero amosar número + Ocultar número pero amosar nome + Ocultar nome e número + + + Bloquear Apps + Bloquear Calendarios + + Histórico de mensaxes + Respostas + Sufixo común + Rexeitar chamada + Actualizar no Pebble + + Opcións de desenvolvedor + Enderezo do MiBand + + Axustes do Pebble + + Monitores de actividade + Monitor de actividade preferido + Sincronizar con Pebble Health + Sincronizar con Misfit + Sincronizar con Morpheuz + + Soporte para chamadas saíntes + Permitir acceso a Apps Android de terceiros + Habilitar soporte experimental para Apps Android que usen PebbleKit + + Mencer e solpor + Enviar mencer e solpor baseado na localización do Pebble + Sincronizar calendario + Borrar automaticamente as notificacións rexeitadas + Modo de privacidade + Notificacións normais + Só amosar o icono de notificación + + Localización + Obter ubicación + Latitude + Lonxitude + Manter actualizada a ubicación + Por favor, active a ubicación por rede + ubicación obtida + + Forzar o protocolo de notificación + Esta opción forza o uso do protocolo de notificación máis recente dependendo da versión do firmware. HABILITE SE SABE O QUE ESTÁ FACENDO! + Habilitar características non probadas + Preferir sempre BLE + Activar rexistro das Apps do dispositivo + Causa que os rexistros das apps do reloxo sexan gardados polo Gadgetbridge (necesita reconectar) + Intentos de reconexión + + Unidades + Formato de hora + Tempo de pantalla acesa + Medición da frecuencia cardíaca durante todo o día + Non conectado + Conectando + Conectado + Estado descoñecido + (descoñecido) + Probar + Probar notificación + Esta é unha notificación de proba desde Gadgetbridge + O Bluetooth non está soportado. + O Bluetooh está desactivado. + Toque nun dispositivo conectado para xestionar as Apps + Toque nun dispositivo conectado para ver a actividade + Toque nun dispositivo conectado para facelo Vibrar + Toque nun dispositivo para conectar + Non foi posíbel conectar. Enderezo Bluetooth inválido? + Gadgetbridge en execución + Instalando binario %1$d/%2$d + Fallou a instalación + Instalación correcta + inicializado + %1$s de %2$s + Descubrir dispositivos + + Parar a busca + Comezar a busca + Conectar novo dispositivo + %1$s (%2$s) + Emparellar dispositivo + Usar o menú de emparellamento Bluetooth de Android para vincular o dispositivo. + Emparella a túa MiBand + Emparellando con %s… + Creando vínculo con %1$s (%2$s) + Incapaz de emparellar con %1$s (%2$s) + Axustes específicos do dispositivo + Deshabilitar isto tamén impedirá o Pebble 2/LE vibrar en chamadas saíntes + + Timeline do Pebble + Enviar os eventos do calendario ao timeline + + As notificacións borranse automáticamente do Pebble cando se rexeitan en Android + + Desplaza a notificación fora da pantalla + Intentar obter a ubicación durante a execución, usar a ubicación almacenada como respaldo + + Activar características non probadas. ¡ACTIVAO SÓ SE SABES O QUE FAS! + Pebble 2/LE límite de GATT MTU + Se o teu Pebble 2/Pebble LE non funciona correctamente, proba esta opción para limitar o MTU (rango válido 20–512) + Anticipar confirmacións do PebbleKit + Axustes de HPlus/Makibes + + ESTÁS INTENTANDO INSTALAR UN FIRMWARE, CONTINÚA BAIXO A TÚA RESPONSABILIDADE +\n +\n +\nEste firmware é para a revisión de HW: %s + Estás a piques de instalar a seguinte app: +\n +\n +\n%1$s Versión %2$s de %3$s +\n + N/D + Asociación en progreso: %1$s (%2$s) + Xa asociado con %1$s (%2$s), conectando … + Nengún enderezo MAC fornecido, non é posíbel emparellar. + Axustes de MiBand / Bip + Home + Muller + Outro + Esquerda + Dereita + Non se proporcionaron datos de usuario válidos, entrementres usaranse dados ficticios. + Cando a súa MiBand vibre e parpadee, toque nela algunhas veces seguidas. + Instalar + Torne o seu dispositivo visíbel. Os dispositivos actualmente vinculados non son normalmente descubertos. Active a localización (i.e, GPS) no Android 6+. Desactive tamén a Protección da Privacidade para o Gadgetbridge, pois esta poderá bloquear e levar ao reinicio do seu teléfono. Se despois de algúns minutos o dispositivo non foi encontrado, tente outra vez após reiniciar o teléfono. + Nota: + Imaxe do dispositivo + Nome/Alcume + Cantidade de vibracións + + Monitor de sono + Escreber arquivos de rexistro + Inicializando + Adquirindo datos de actividade + De %1$s até %2$s + De que lado a usa? + Perfil de vibración + + Moi curto + Curto + Medio + Longo + Moi longo + Timbre + Alarma + Vibración + + Probar + Notificación de SMS + Axustes de vibración + Notificación xenérica + Notificación de correo electrónico + Notificación de chamada entrante + Conversas + Navegación + Rede social + + Zonas de velocidade + Minutos totais + Pasos por minuto + + Atopar dispositivo perdido + Cancelar para deter a vibración. + A túa actividade + Configurar alarmas + Configurar alarmas + Detalles da alarma + Dom + Lun + Mar + Mér + Xov + Ven + Sáb + Espertador intelixente + Houbo un erro configurando as alarmas, por favor téntao de novo! + Alarmas enviadas ao dispositivo! + Sen datos. Sincronizar dispositivo? + A piques de transferir %1$s de datos desde %2$s + Obxectivo de pasos diarios + Erro executando \'%1$s\' + A túa actividade (ALPHA) + Imposíbel conectar: %1$s + Non foi posíbel atopar un controlador para instalar o arquivo. + Imposíbel instalar o arquivo fornecido: %1$s + Non é posíbel instalar o firmware fornecido: non corresponde coa versión do hardware do seu Pebble. + Por favor, agarda mentres se determina o estado da instalación… + Dispositivo con batería baixa! + %1$s bateria restante: %2$s%% + Última carga: %s +\n + Número de cargas: %s + O teu sono + Sono na semana + Sono hoxe, obxectivo: %1$s + Pasos na semana + A túa actividade e sono + Anovando Firmware… + O arquivo non pode ser instalado, o dispositivo non está preparado. + Firmware do Mi Band %1$s + Firmware do Amazfit Bip %1$s + Versión compatíbel + Versión sen probar! + Conexión ao dispositivo: %1$s + Firmware do Pebble %1$s + Revisión de hardware correcta + Revisión de hardware inválida! + %1$s (%2$s) + Ocorreu un erro coa transferencia do firmware. NON REINICIE a súa Mi Band! + Problemas ao transferir os metadados do firmware + Instalación do Firmware completa + Instalación do Firmware completa, reiniciando o dispositivo… + Fallou a escritura do firmware + Pasos + Calorías + Distancia + Reloxo + Ritmo cardíaco + Batería + Actividade en tempo real + Pasos hoxe, obxectivo: %1$s + Non confirmar a transferencia de datos de actividade + Se os datos de actividade non foran confirmados ao dispositivo, non serán borrados del. Útil se o GB é utilizado en simultáneo con outras aplicacións. + Manterá os datos na Mi Band mesmo despois da sincronización. Útil se o GB é utilizado en simultáneo con outras aplicacións. + Usar modo de baixa latencia para anovacións de Firmware + Isto pode axudar en dispositivos nos que fallen as actualizacións de firmware + + Historial de pasos + Pasos/min actuais + Pasos totais + Historial de pasos por minuto + Comeza a túa actividade + Actividade + Sono leve + Sono profundo + Sen usar + Non conectado. + Todas as alarmas deshabilitadas + Manter datos de actividade no dispositivo + Firmware incompatíbel + Este firmware non é compatíbel co dispositivo + Alarmas reservadas para próximos eventos + Usar sensor de ritmo cardíaco para mellorar a detección de sono + Mi2: Formato de data + Hora + Hora e data + Accións do botón + Especificar accións para pulsación do botón do Mi Band 2 + Conta de pulsacións do botón + Número de pulsacións no botón para activar o envío da mensaxe + Mensaxe a enviar + Activar acción do botón + Activar acción para un número definido de pulsacións de botón + Activar vibración da banda + Activar vibración da banda en acción desencadenada por botón + Retardo máximo entre pulsacións + Retardo máximo en milisegundos entre pulsacións de botón + Retardo despois da acción do botón + Notificación de obxectivo + A pulseira vai vibrar cando se acade o obxectivo diario de pasos + Elementos a amosar + Escolle os elementos a amosar na pantalla da banda + Activar pantalla ao levantar + Rodar o pulso para mudar a información + Non Molestar + A banda non recibirá notificacións mentres está activa + Avisos de inactividade + A pulseira vai vibrar se estiveches inactivo durante algún tempo + Limite de inactividade (en minutos) + Desactivar os avisos de inactividade durante un intervalo de tempo + Hora de inicio + Hora de fin + A piques de transferir dados desde %1$s + + Agardando reconexión + + Sobre ti + Ano de nacemento + Xénero + Altura en cm + Peso en kg + + Autenticando + Requerida autenticación + + ZzZ + Engadir widget + Duración de sono preferida en horas + Unha alarma foi definida para %1$02d:%2$02d + Revisión de hardware: %1$s + Versión de firmware: %1$s + Erro creando un cartafol para os arquivos de rexistro: %1$s + "Versión HW: " + Anovación de Firmware en progreso + Firmware non enviado + Ritmo cardíaco + Ritmo cardíaco + + Almacenar rexistro en bruto na base de datos + Xestión da base de datos + Xestión da base de datos + A base de datos usa a seguinte ubicación no seu dispositivo. +\nEsta ubicación é accesíbel para outras aplicacións Android e para o seu ordenador. +\nAtopará as súas bases de datos exportadas (ou a que quere importar) aquí: + Borrar a base de datos antiga + Non se pode acceder á ruta para exportar . Por favor, contacta cos desenvolvedores. + Exportado a: %1$s + Erro ao exportar a base de datos: %1$s + Erro exportando as preferencias: %1$s + Importar datos? + Quere sobreescribir a base de datos actual? Perderanse todos os datos actuais de actividade (de habelos). + Importado con éxito. + Erro ao importar a base de datos: %1$s + Erro importando preferencia: %1$s + Borrar datos de actividade? + Realmente quere borrar toda a base de datos? Perderanse todos os seus datos de actividade e a información sobre os seus dispositivos. + Datos eliminados con éxito. + Fallou o borrado da base de datos. + Borrar a antiga base de datos de actividade? + Está seguro de querer borrar a antiga base de datos de actividade? Perderanse os datos de actividade que non foran importados. + Datos antigos de actividade borrados con éxito. + Fallou o borrado da base de datos de actividades antigas. + Sobreescribir + Cancelar + Borrar + + Vibración + + Emparellando co Pebble + Activar o emparellamento Bluetooth + Desactiva isto se tes problemas de conexión + + Métrico + Imperial + + 24H + AM/PM + Espertador + (%1$s) + Atopado! + Mi2: formato de hora + Debe instalar a versión %1$s antes de instalar este firmware! + Notificacións de texto + Require firmware >= 1.0.1.28 e Mili_pro.ft* instalado. + Apagado + Apagado + Automático (detección de sono) + Axendado (intervalo de tempo) + Intentando emparellar con %1$s + O enlace con %1$s fallou de inmediato. + Intentando conectar con: %1$s + Active o Bluetooth para achar dispositivos. + Correctamente enlazado con %1$s. + Emparellar con %1$s? + Emparellar + Non emparellar + + Abrir no teléfono + Silenciar + Responder +Estás a piques de instalar o firmware %s nas veces do que está actualmente no teu Amazfit Bip. +\n +\nPor favor, asegúrate de instalar o firmware .gps, despois o arquivo .res, e finalmente o binario .fw. O teu reloxo reiniciará despois de instalar o arquivo .fw. +\n +\nNota: non tes que instalar .res e .gps se estes arquivos son os mesmos que os previamente instalados. +\n +\nEXPERIMENTAL, PROCEDE BAIXO A TÚA PROPIA RESPONSABILIDADE + Use soporte experimental do Pebble LE nas veces do Bluetooth clásico, require emparellar un \"Pebble LE\" despois de que nengún LE teña sido conectado + Isto fará que as mensaxes enviadas para apps de terceiros sexan recoñecidas sempre e inmediatamente + + Compensación da hora do dispositivo en horas (para detectar o sono de traballadores a turnos) + Enviar mensaxe despois dun número definido de pulsacións + Retardo despois dunha acción do botón (o número está en button_id intent extra) ou ben 0 para efecto inmediato + Se marcado, os datos son almacenados no seu formato orixinal ficando disponíbeis para posterior interpretación. Nota: A base de datos será máis grande! + É esperado que vexa unha notificación de emparellamento no seu dispositivo Android. Se iso non acontece, acceda ás notificacións e acepte a solicitude de emparellamento. Despois acepte igualmente a solicitude de emparellamento no seu Pebble + + Asegúrate de que este tema esté activado na aplicación de notificación do clima para obter a información no teu Pebble. +\n +\nNon se require configuración. +\n +\nPodes activar a aplicación meteorolóxica do sistema desde a configuración da app. +\n +\nAs \"watchfaces\" soportadas amosarán a información do clima automáticamente. + Selecciona Emparellar para emparellar os teus dispositivos. Se isto falla, proba de novo sen emparellar. + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 3cd65cb67..21c1ddf1a 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -5,6 +5,7 @@ Impostazioni Debug Esci + Donazioni Sincronizza Monitoraggio del sonno (ALPHA) Trova dispositivo smarrito @@ -40,6 +41,8 @@ Sposta in cima Blocco notifiche + + Calendari ignorati Installazione FW/App Il firmware %s verrà installato al posto di quello attualmente sulla Mi Band. @@ -74,7 +77,7 @@ Messaggi Pebble Supporto per applicazioni che inviano le notifiche a Pebble usando PebbleKit. Notifiche generiche - …anche se lo schermo è acceso + … anche se lo schermo è acceso Non disturbare Non inviare notifiche nei periodi configurati come \"non disturbare\". Traslitterazione @@ -108,6 +111,7 @@ Attiva l\'accesso sperimentale ad applicazioni Android che usano PebbleKit Alba e tramonto Mostra gli orari calcolati per l\'alba e il tramonto sulla timeline + Sincronizza calendario Rimuovi automaticamente le notifiche Le notifiche vengono rimosse automaticamente dal Pebble dopo averle nascoste sul dispositivo Android Impostazioni privacy @@ -135,6 +139,7 @@ Invia l\'ACK dei messaggi PebbleKit prematuramente I messaggi inviati alle applicazioni di terze parti verranno immediatamente confermate Tentativi di riconessione + Unità Formato dell\'orario Durata dell\'accensione dello schermo @@ -143,8 +148,6 @@ Non connesso In collegamento Connesso - Stato sconosciuto - HW: %1$s FW: %2$s Versione firmware: %1$s (sconosciuto) Test @@ -219,6 +222,9 @@ Cha Navigazione Social Network + Zone di velocità + Minuti totali + Passi al minuto Trova dispositivo smarrito Annulla per fermare la vibrazione Le tue attività @@ -257,6 +263,7 @@ Aggiornamento del Firmware... Il file non può essere installato, il dispositivo non è pronto. Firmware Mi Band: %1$s + Firmware Amazfit Bip %1$s Versione compatibile Versione non testata! Connessione al device: %1$s @@ -270,6 +277,11 @@ Installazione del firmware completata, riavvio del dispositivo... Scrittura del firmware non riuscita Passi + Calorie + Distanza + Orologio + Pulsazioni + Batteria Attività in tempo reale Passi di oggi, traguardo: %1$s Non confermare il trasferimento dati @@ -296,7 +308,21 @@ Sfasamento dell\'orario per il device (per consentire il rilevamento del sonno per chi lavora a turni) Mi2: formato della data Ora + Ora e data + Notifica raggiungimento obbiettivi + La band vibrerà una volta raggiunto l\'obbiettivo passi giornalieri + Elementi mostrati + Scelta elementi da mostrare sullo schermo della band Attiva il display quando sollevato + Rotazione polso per cambiare vista + Non disturbare + La band non riceverà notifiche quando è attiva + Allarmi inattività + La band vibrerà dopo un determinato periodo di inattività + Limite inattività (in minuti) + Disabilita allarmi inattività per un dato intervallo di tempo + Ora inizio + Ora fine Vengono trasferiti dati a partire dal %1$s in attesa di riconnessione Informazioni sull\'utilizzatore @@ -327,10 +353,12 @@ Impossibile accedere al path esterno. Per cortesia contatta gli sviluppatori del progetto. Esportato su: %1$s Errore esportando il DB: %1$s + "Errore nell\'esportazione delle preferenze: %1$s" Importare i dati? Vuoi davvero sovrascrivere il database attuale? Tutte le tue attività saranno perse. L\'importazione ha avuto successo. Errore importando il DB: %1$s + "Errore nell\'importazione della preferenza: %1$s" Cancella i dati delle attività? Vuoi davvero cancellare il database? Tutte le informazioni sulle tue attività e sui tuoi dispositivi saranno perse. Dati cancellati con successo. @@ -359,26 +387,13 @@ Mi2: Formato dell\'orario E\' necessario installare la verione %1$s prima di installare questo firmware! Notifiche - Spento -Dona - Calendari con notifiche bloccate - - Blocca calendari - - Sincronizza calendari - Mostra eveti dei calendari sulla timeline - - Minuti totali - Passi al minuto - - Calorie - Distanza - Batteria - Ora e data - Notifica al raggiungimento dell\'obiettivo - Il dispositivo vibrerà quando l\'obiettivo giornaliero di passi viene raggiunto - Non disturbare - Errore durante l\'export delle preferenze: %1$s - Errore durante l\'importazione delle preferenze: %1$s - "Devono essere installati un firmware >= 1.0.1.28 e Mili_pro.ft*." - + Richiede firmware versione minima 1.9.1.28 e Mili_pro.ft* installati. + spento + Spento + Automatico (rilevamento del sonno) + Tentativo di connessione a %1$s in corso + La connessione con %1$s è subito fallita. + Tentativo di connessione con: %1$s + Abilitare Bluetooth per l\'individuazione dei dispositivi. + Non connettersi + \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 7e92403a0..e3e99dd70 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -25,8 +25,10 @@ 지금 기존의 Mi Band 펌웨어 대신 %s 펌웨어를 설치하려고 합니다. 지금 Mi Band의 기존 펌웨어 대신 %1$s와 %2$s 펌웨어를 설치하려고 합니다. 이 펌웨어는 테스트를 거쳤고 가젯브릿지와 호환됩니다. - 이 펌웨어는 테스트를 거치지 않았고 가젯브릿지와 호환되지 않을 수 있습니다.\n\n Mi Band에 설치하지 않는 것이 좋습니다. - 여전히 계속 진행하기를 원하고 이후에도 정상적으로 작동하기를 원한다면, %s 화이트리스트 펌웨어 버전을 가젯브릿지 개발자들에게 알려주세요. + 이 펌웨어는 테스트를 거치지 않았고 가젯브릿지와 호환되지 않을 수 있습니다. +\n +\nMi Band에 설치하지 않는 것이 좋습니다! + 여전히 계속 진행하기를 원하고 이후에도 정상적으로 작동하기를 원한다면, %s 화이트리스트 펌웨어 버전을 가젯브릿지 개발자들에게 알려주세요 설정 일반 설정 @@ -55,7 +57,7 @@ 개발자 옵션 Mi Band 주소 Pebble 설정 - 선호하는 액티비티 트래커 + 선호하는 활동 추적자 제 3자 안드로이드 앱 접근을 허용 PebbleKit을 사용하는 안드로이드 앱을 실험적으로 지원 강제 알림 프로토콜 @@ -221,4 +223,108 @@ 삭제 - +기부 + 기기 제거 + %1$s 제거 + 기기와 그와 관련된 모든 데이터를 삭제 할 것입니다! + 네비게이션 드로어 열기 + 네비게이션 드로어 닫기 + 카드를 길게 눌러 연결 해제 + 연결 해제 중 + 연결 중 + 기기의 스크린샷을 찍는 중 + + + 캐시에 저장된 앱들 + 설치된 앱들 + 설치된 워치페이스 + 삭제하고 캐시에서 제거 + 페블 앱스토어에서 검색 + 심장 박동수 모니터링 활성화 + 심장 박동수 모니터링 비활성화 + 시스템 날씨 앱 활성화 + 시스템 날씨 앱 비활성화 + 날씨 알림 앱 설치 + 가장 위로 이동 + + 비활성화 된 캘린더들 + + 지금 기존의 Amazfit Bip에 설치되어 있는 펌웨어 대신 %s 펌웨어를 설치하려고 합니다. +\n +\n.gps 펌웨어를 설치하고 나서 .res 파일을, 그리고 최종적으로 .fw 파일을 설치하세요. 시계가 .fw 파일을 설치한 후 재시작 됩니다. +\n +\n비고: 만약 전에 설치되어 있던 .res 파일과 .gps 파일을 그대로 사용한다면 각 파일을 다시 설치 할 필요는 없습니다. +\n +\n실험적인 기능입니다. 기기 파손의 책임은 사용자에게 있으니 주의해서 진행하세요 + 자동으로 시작 + 언어 + + 가젯브릿지 알림 숨김 + 상태 표시줄과 잠금 화면 알림에 아이콘이 표시됩니다 + 상태 표시줄과 잠금 화면 알림에 아이콘이 표시되지 않습니다 + + PebbleKit를 사용하여 페블에 알림을 보내는 앱을 지원합니다. + 방해 금지 모드 + 방해 금지 모드가 활성화 되었을 때 원하지 않는 알림을 보내지 않습니다 + 표기법 변환 + 만약 장치가 당신이 사용하는 언어의 폰트를 지원하지 않는다면 이 옵션을 활성화 하세요 + + 프라이버시 + 전화 프라이버시 모드 + 이름과 번호를 표시 + 이름을 숨기지만 번호는 표시 + 이름을 표시하지만 번호는 숨기기 + 이름과 번호를 숨기기 + + + 블랙리스트 캘린더 + + 상용구 + 답장 + 전화 거절 + 페블에서 업데이트 + + 활동 추적자 설정 + Pebble Health 동기화 + Misfit 동기화 + Morpheuz 동기화 + + 거는 전화를 지원 + 이 옵션을 비활성화 하면 Pebble 2/LE가 전화를 걸 때 진동하지 않습니다 + + 페블 타임라인 + 일출과 일몰 + 현재 위치 기반으로 타임라인에 일출과 일몰 시간을 보여줍니다 + 캘린더 동기화 + 타임라인에 캘린더 이벤트를 보여줍니다 + + 닫은 알림을 자동으로 삭제 + 안드로이드 기기에서 알림을 닫으면 페블에서도 자동으로 삭제합니다 + + 프라이버시 모드 + 일반 알림 + 알림 내용을 바로 보여주지 않습니다 + 알림 아이콘만 보여줍니다 + + 위치 + 위치 찾기 + 위도 + 경도 + 위치를 자동으로 업데이트 + 사용 중 현재 위치를 받아오려고 시도합니다. 실패하면 저장된 위치를 사용합니다 + + 네트워크 기반 위치 측량 기능을 활성화 해 주세요 + 위치를 찾음 + + 블루투스 저전력 연결을 언제나 선호 + 일반 블루투스 프로토콜이 아닌 실험적인 블루투스 저전력 연결을 사용합니다. 일반적인 연결을 최소한 한번 한 후 Pebble LE를 페어링 하는 것이 필요합니다 + Pebble 2/LE GATT MTU 제한 + 만약 Pebble 2/Pebble LE가 기대한 대로 작동하지 않는다면, 이 설정을 사용하여 MTU를 제한하세요 (20-512까지 설정 가능) + 시계 앱 로그 활성화 + 시계 앱들의 로그를 Gadgetbridge가 기록합니다 (재연결 필요) + 끝나기 전 미리 PebbleKit 연결에 ACK 보내기 + 제 3자 앱으로 보내는 메세지에 언제나 바로 ACK을 보냅니다 + + 단위 + 시간 형식 + diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml new file mode 100644 index 000000000..2e28dea09 --- /dev/null +++ b/app/src/main/res/values-nl/strings.xml @@ -0,0 +1,129 @@ + +Gadgetbridge + + Gadgetbridge + Instellingen + Debug + Sluit af + Doneer + Synchroniseren + Slaap monitor (ALPHA) + Zoek verloren Toestel + Screenshot maken + Loskoppelen + Apparaat Verwijderen + Verwijder %1$s + Dit zal het apparaat en alle bijbehorende gegevens verwijderen! + Druk lang op de kaart om los te koppelen + Loskoppelen + Aansluiten + Een screenshot maken van het apparaat + + + Debug + + + Apps in de cache + Geïnstalleerde apps + Geïnstalleerde wijzerplaten + Verwijder + Verwijder en verwijder uit cache + Installeer + Zoek in Pebble Appstore + Activeren + deactiveren + Activeer HRM + Deactiveer HRM + Activeer het systeem weer app + Installeer de app voor weerbericht + Configureren + Verplaats naar de top + + Notificatie zwarte lijst + + U staat op het punt om firmware% s te installeren in plaats van die nu op uw Mi Band staat. + U staat op het punt om firmware% s te installeren in plaats van die nu op uw Amazfit Bip staat. +\n +\nZorg ervoor dat u de .gps firmware installeert, dan het .res-bestand en tenslotte het .fw-bestand. Uw horloge wordt opnieuw gestart na het installeren van het .fw-bestand. +\n +\nOpmerking: u hoeft geen .res en .gps te installeren als deze bestanden exact hetzelfde zijn als die welke eerder zijn geïnstalleerd. +\n +\nEXPERIMENTEEL, PROCEDURE OP JE EIGEN RISICO + U staat op het punt om firmwares %1$s en %2$s te installeren in plaats van die momenteel op uw Mi Band staat. + Deze firmware is getest en is compatibel met Gadgetbridge. + Deze firmware is niet getest en is mogelijk niet compatibel met Gadgetbridge. +\n +\nU wordt NIET aangemoedigd om het op uw Mi Band te installeren! + Als u nog steeds wilt doorgaan en uw apparaat blijft goed functioneren, vertelt u de ontwikkelaars van Gadgetbridge om de firmware-versie op de goedgekeurde lijst te zetten: %s + + Instellingen + + Algemene instellingen + Verbind met apparaat wanneer Bluetooth is ingeschakeld + Start automatisch + Verbind automatisch opnieuw + Gewenste Audiospeler + Standaard + + Datum en tijd + Sync tijd + Synchroniseer de tijd naar het apparaat wanneer u verbinding maakt en wanneer de tijd of tijdzone op Android verandert + + Thema + Licht + Donker + + Taal + + Verberg de Gadgetbridge-melding + Het pictogram in de statusbalk en de melding op het vergrendelingsscherm worden weergegeven + Het pictogram in de statusbalk en de melding op het vergrendelingsscherm zijn verborgen + + Meldingen + Herhalingen + Telefoongesprekken + SMS + Pebble Berichten + Ondersteuning voor applicaties die meldingen versturen naar de Pebble via PebbleKit sturen. + Algemene melding ondersteuning + ... ook als het scherm is ingeschakeld + Niet Storen + Stop ongewenste meldingen te verzenden op basis van de Niet storen modus + Transliteratie + Schakel dit in als uw apparaat geen ondersteuning heeft voor het lettertype van uw taal + + Altijd + Wanneer het scherm uit staat + Nooit + + Privacy + Bel privacy modus + Toon naam en nummer + Verberg naam maar toon nummer + Verberg nummer maar toon naam + Verberg naam en nummer + + + Blacklist Apps + Blacklist Kalenders + + Antwoorden + Vaak voorkomend achtervoegsel + Bel Afwijzing + Update op Pebble + + Ontwikkelaarsopties + Mi Band adres + + Pebble instellingen + + Activity trackers + Gewenste activiteit tracker + Synchroniseer Pebble Health + Sync Misfit + Sync Morpheuz + + Ondersteun uitgaande oproepen + Als u dit uitschakelt, wordt ook de Pebble 2/LE gestopt om voor uitgaande gesprekken te trillen + + diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/PebbleUtilsTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/PebbleUtilsTest.java new file mode 100644 index 000000000..6fce5d43f --- /dev/null +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/PebbleUtilsTest.java @@ -0,0 +1,54 @@ +package nodomain.freeyourgadget.gadgetbridge.test; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor; +import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils; + +import static org.junit.Assert.assertEquals; + +public class PebbleUtilsTest extends TestBase { + @Test + public void testHexToPebbleColorConversion() { + Map testCases = new HashMap<>(); + + testCases.put("#000000", PebbleColor.Black); + testCases.put("#ffffff", PebbleColor.White); + testCases.put("#00ff00", PebbleColor.Green); + + testCases.put("#452435", PebbleColor.Black); + testCases.put("#334afd", PebbleColor.DukeBlue); + testCases.put("#ccb75c", PebbleColor.Brass); + testCases.put("#1b1c94", PebbleColor.OxfordBlue); + testCases.put("#90f892", PebbleColor.MayGreen); + testCases.put("#ff7301", PebbleColor.Orange); + + testCases.put("#00aa00", PebbleColor.IslamicGreen); + + for (String colorKey : testCases.keySet()) { + byte evaluatedColor = PebbleUtils.getPebbleColor(colorKey); + assertEquals("Color " + colorKey + " failed to translate properly!", + testCases.get(colorKey).byteValue(), evaluatedColor); + } + } + + @Test + public void testIntToPebbleColorConversion() { + Map testCases = new HashMap<>(); + + testCases.put(0x000000, PebbleColor.Black); + testCases.put(0xffffff, PebbleColor.White); + testCases.put(0x00ff00, PebbleColor.Green); + + testCases.put(0x00aa00, PebbleColor.IslamicGreen); + + for (int colorKey : testCases.keySet()) { + byte evaluatedColor = PebbleUtils.getPebbleColor(colorKey); + assertEquals("Color " + Integer.toHexString(colorKey) + " failed to translate properly!", + testCases.get(colorKey).byteValue(), evaluatedColor); + } + } +}