diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9597b4a212..6ce050552c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,7 @@ + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java index 29608fc459..671dc95622 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java @@ -32,6 +32,12 @@ public final class MiBandConst { public static final String PREF_MIBAND_ALARMS = "mi_alarms"; public static final String PREF_MIBAND_DONT_ACK_TRANSFER = "mi_dont_ack_transfer"; public static final String PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR = "mi_reserve_alarm_calendar"; + public static final String PREF_MIBAND_BUTTON_ACTION_MENU_ENABLE = "mi2_enable_button_action_menu"; + public static final String PREF_MIBAND_MENU_ELEMENTS = "mi2_menu_elements"; + public static final String PREF_MIBAND_MENU_FORWARD = "mi2_menu_forward"; + public static final String PREF_MIBAND_MENU_BACKWARD = "mi2_menu_backward"; + public static final String PREF_MIBAND_MENU_VALIDATE = "mi2_menu_validate"; + public static final String PREF_MIBAND_MENU_WAKELOCK = "mi2_menu_wakelock"; public static final String PREF_MIBAND_BUTTON_ACTION_ENABLE = "mi2_enable_button_action"; public static final String PREF_MIBAND_BUTTON_ACTION_VIBRATE = "mi2_button_action_vibrate"; public static final String PREF_MIBAND_BUTTON_PRESS_COUNT = "mi_button_press_count"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java index c9ca6d1f8b..f98781e42d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.PowerManager; import android.text.format.DateFormat; import android.widget.Toast; @@ -104,6 +105,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile; import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.actions.StopNotificationAction; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2CustomMenuNotificationStrategy; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2NotificationStrategy; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2TextNotificationStrategy; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchActivityOperation; @@ -147,6 +149,15 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport { private static long currentButtonPressTime = 0; private static long currentButtonTimerActivationTime = 0; + private static Boolean browsingCustomMenu = false; + private static int currentOptionId = 0; + private static Timer customMenuTimer = null; + private static Timer customMenuTimerTimeout = null; + + // To make Timers work correctly when Android device is in deep sleep + // @see https://stackoverflow.com/questions/47472741/timer-not-expiring-precisely-during-sleep-state-in-android + private static PowerManager.WakeLock deviceWakeLock = null; + private static final Logger LOG = LoggerFactory.getLogger(HuamiSupport.class); private final DeviceInfoProfile deviceInfoProfile; private final IntentListener mListener = new IntentListener() { @@ -340,7 +351,9 @@ public NotificationStrategy getNotificationStrategy() { return new Mi2NotificationStrategy(this); } } - if (GBApplication.getPrefs().getBoolean(MiBandConst.PREF_MI2_ENABLE_TEXT_NOTIFICATIONS, true)) { + if (browsingCustomMenu) { + return new Mi2CustomMenuNotificationStrategy(this); + } else if (GBApplication.getPrefs().getBoolean(MiBandConst.PREF_MI2_ENABLE_TEXT_NOTIFICATIONS, true)) { return new Mi2TextNotificationStrategy(this); } return new Mi2NotificationStrategy(this); @@ -1050,6 +1063,123 @@ public void runButtonAction() { currentButtonPressTime = System.currentTimeMillis(); } + public void runCustomMenu() { + Prefs prefs = GBApplication.getPrefs(); + + + if(this.getCustomMenuElements().length > 0) { + /*if (currentButtonTimerActivationTime != currentButtonPressTime) { + return; + }*/ + LOG.info("Running custom menu"); + browsingCustomMenu = true; + currentButtonActionId = 0; + this.displayCustomMenuOption(0); + } + } + + private void displayCustomMenuOption(int optionId) { + Prefs prefs = GBApplication.getPrefs(); + + currentOptionId = optionId; + + String eltLabel = this.getCustomMenuCurrentElement()[0]; + LOG.info("Sending Menu Option " + eltLabel); + try { + TransactionBuilder builder = performInitialized("sending menu option #" + eltLabel); + VibrationProfile profile = getPreferredVibrateProfile("Custom Menu", prefs, (short) 0); + profile.setAlertLevel(HuamiService.ALERT_LEVEL_MESSAGE); + SimpleNotification simpleNotification = new SimpleNotification(eltLabel, null, null/*NotificationType.UNKNOWN*/); + sendCustomNotification(profile, simpleNotification, 0, 0, 0, 0, null, builder); + builder.queue(getQueue()); + } catch (IOException ex) { + LOG.error("Unable to send notification to MI device", ex); + } finally { + currentButtonPressCount = 0; + currentButtonPressTime = System.currentTimeMillis(); + } + } + + private void runCustomMenuCurrentAction() { + Prefs prefs = GBApplication.getPrefs(); + + /*if (currentButtonTimerActivationTime != currentButtonPressTime) { + return; + }*/ + + String elementMessage = this.getCustomMenuCurrentElement()[1]; + Intent in = new Intent(); + in.setAction(elementMessage); + in.putExtra("button_id", currentButtonActionId); + LOG.info("Sending " + elementMessage + " with button_id " + currentButtonActionId); + this.getContext().getApplicationContext().sendBroadcast(in); + + LOG.info("Sending Confirmation"); + try { + TransactionBuilder builder = performInitialized("sending custom menu action confirmation"); + VibrationProfile profile = getPreferredVibrateProfile("Custom Menu", prefs, (short) 0); + profile.setAlertLevel(HuamiService.ALERT_LEVEL_MESSAGE); + SimpleNotification simpleNotification = new SimpleNotification("OK", null, null/*NotificationType.UNKNOWN*/); + sendCustomNotification(profile, simpleNotification, 0, 0, 0, 0, null, builder); + builder.queue(getQueue()); + } catch (IOException ex) { + LOG.error("Unable to send notification to MI device", ex); + } finally { + browsingCustomMenu = false; + currentButtonActionId = 0; + + currentButtonPressCount = 0; + currentButtonPressTime = System.currentTimeMillis(); + } + } + + // TODO custom separator ? + private String[] getCustomMenuElements() { + Prefs prefs = GBApplication.getPrefs(); + return prefs.getString(MiBandConst.PREF_MIBAND_MENU_ELEMENTS, "").split(";"); + } + + // TODO custom separator ? + private String[] getCustomMenuCurrentElement() { + return getCustomMenuElements()[currentOptionId].split(":"); + } + + private int getCustomMenuNextOptionId() { + int menuElementCount = this.getCustomMenuElements().length; + return (currentOptionId + 1) % menuElementCount; + } + + private int getCustomMenuPreviousOptionId() { + int menuElementCount = this.getCustomMenuElements().length; + return (menuElementCount + currentOptionId - 1) % menuElementCount; + } + + private Timer resetCustomMenuTimerTimeout() { + Prefs prefs = GBApplication.getPrefs(); + final Boolean isWakelockEnabled = prefs.getBoolean(MiBandConst.PREF_MIBAND_MENU_WAKELOCK, false); + + if(customMenuTimerTimeout != null) { + customMenuTimerTimeout.cancel(); + } + + // TODO Mi band 2 seems to display notifications only during 5 secs + // This isn't very clean, but i couldn't find anything else. + customMenuTimerTimeout = new Timer("Mi Band Button Action Custom Menu Timeout Timer"); + customMenuTimerTimeout.schedule(new TimerTask() { + @Override + public void run() { + LOG.info("Deactivating custom menu due to timeout"); + browsingCustomMenu = false; + if (isWakelockEnabled && deviceWakeLock != null && deviceWakeLock.isHeld()) { + LOG.info("Releasing Wakelock"); + deviceWakeLock.release(); + } + customMenuTimerTimeout.cancel(); + } + }, 5000); + return customMenuTimerTimeout; + } + public void handleDeviceEvent(byte[] value) { if (value == null || value.length == 0) { return; @@ -1169,6 +1299,9 @@ public void handleButtonEvent() { int buttonActionDelay = prefs.getInt(MiBandConst.PREF_MIBAND_BUTTON_ACTION_DELAY, 0); int requiredButtonPressCount = prefs.getInt(MiBandConst.PREF_MIBAND_BUTTON_PRESS_COUNT, 0); + final Boolean isCustomMenuEnabled = prefs.getBoolean(MiBandConst.PREF_MIBAND_BUTTON_ACTION_MENU_ENABLE, false); + final Boolean isWakelockEnabled = prefs.getBoolean(MiBandConst.PREF_MIBAND_MENU_WAKELOCK, false); + if (requiredButtonPressCount > 0) { long timeSinceLastPress = System.currentTimeMillis() - currentButtonPressTime; @@ -1181,10 +1314,12 @@ public void handleButtonEvent() { } currentButtonPressTime = System.currentTimeMillis(); - if (currentButtonPressCount == requiredButtonPressCount) { + if (currentButtonPressCount == requiredButtonPressCount && !browsingCustomMenu) { currentButtonTimerActivationTime = currentButtonPressTime; + if (buttonActionDelay > 0) { LOG.info("Activating timer"); + final Timer buttonActionTimer = new Timer("Mi Band Button Action Timer"); buttonActionTimer.scheduleAtFixedRate(new TimerTask() { @Override @@ -1198,8 +1333,66 @@ public void run() { LOG.info("Activating button action"); runButtonAction(); } + + if (isCustomMenuEnabled) { + if(isWakelockEnabled) { + LOG.info("Acquiring wakelock"); + PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); + deviceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport:wakelock"); + deviceWakeLock.acquire(); + } + final Timer customMenuTimer = new Timer("Mi Band Button Action Custom Menu"); + customMenuTimer.schedule(new TimerTask() { + @Override + public void run() { + LOG.info("Activating custom menu"); + resetCustomMenuTimerTimeout(); + runCustomMenu(); + customMenuTimer.cancel(); + } + }, buttonActionDelay); + } + currentButtonActionId++; currentButtonPressCount = 0; + } else if (browsingCustomMenu) { + if (customMenuTimer != null) { + customMenuTimer.cancel(); + } + + final int moveForwardTapCount = prefs.getInt(MiBandConst.PREF_MIBAND_MENU_FORWARD, 1); + final int moveBackwardTapCount = prefs.getInt(MiBandConst.PREF_MIBAND_MENU_BACKWARD, 2); + final int validateTapCount = prefs.getInt(MiBandConst.PREF_MIBAND_MENU_VALIDATE, 3); + + customMenuTimer = new Timer("Mi Band Button Action Custom Menu Timer"); + customMenuTimer.schedule(new TimerTask() { + @Override + public void run() { + if (currentButtonPressCount == moveForwardTapCount) { + resetCustomMenuTimerTimeout(); + displayCustomMenuOption(getCustomMenuNextOptionId()); + currentButtonActionId++; + currentButtonPressCount = 0; + } else if (currentButtonPressCount == moveBackwardTapCount) { + resetCustomMenuTimerTimeout(); + displayCustomMenuOption(getCustomMenuPreviousOptionId()); + currentButtonActionId++; + currentButtonPressCount = 0; + } else if (currentButtonPressCount == validateTapCount) { + currentButtonTimerActivationTime = currentButtonPressTime; + customMenuTimerTimeout.cancel(); + LOG.info("Selected action on custom menu"); + runCustomMenuCurrentAction(); + currentButtonActionId++; + currentButtonPressCount = 0; + if (isWakelockEnabled && deviceWakeLock != null && deviceWakeLock.isHeld()) { + LOG.info("Releasing wakelock"); + deviceWakeLock.release(); + } + } + customMenuTimer.cancel(); + } + }, buttonPressMaxDelay); } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2CustomMenuNotificationStrategy.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2CustomMenuNotificationStrategy.java new file mode 100644 index 0000000000..cb52aab983 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/miband2/Mi2CustomMenuNotificationStrategy.java @@ -0,0 +1,60 @@ +/* Copyright (C) 2017-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2; + +import android.bluetooth.BluetoothGattCharacteristic; + +import androidx.annotation.NonNull; +import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile; +import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; +import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; +import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory; +import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile; +import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert; +import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.OverflowStrategy; +import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiIcon; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport; +import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; + +public class Mi2CustomMenuNotificationStrategy extends Mi2NotificationStrategy { + public Mi2CustomMenuNotificationStrategy(HuamiSupport support) { + super(support); + } + + @Override + protected void sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, BtLEAction extraAction, TransactionBuilder builder) { + // announce text messages with configured alerts first + // TODO this seems non-necessary + //super.sendCustomNotification(vibrationProfile, simpleNotification, extraAction, builder); + // and finally send the text message, if any + if (simpleNotification != null && !StringUtils.isEmpty(simpleNotification.getMessage())) { + sendAlert(simpleNotification, builder); + } + } + + protected void sendAlert(@NonNull SimpleNotification simpleNotification, TransactionBuilder builder) { + AlertNotificationProfile profile = new AlertNotificationProfile<>(getSupport()); + // override the alert category, since only SMS and incoming call support text notification + AlertCategory category = AlertCategory.SMS; + NewAlert alert = new NewAlert(category, 1, simpleNotification.getMessage()); + profile.newAlert(builder, alert, OverflowStrategy.MAKE_MULTIPLE); + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index caf9172cea..6f9ce777fa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -435,6 +435,18 @@ Disconnect notification Button actions Specify actions on Mi Band 2 button press + Custom Menu + Create a custom menu on Mi Band 2 + Menu Elements + The elements of your menu (label1:message1;label2:message2;...) + Move forward + How many taps to move forward in the menu + Move backward + How many taps to move backward in the menu + Validate current option + How many taps to confirm selected menu option + Enable WakeLock + Enable if you have issues when phone sleeps. WARNING : this may reduce your battery life. Button press count Number of button presses to trigger message broadcast Broadcast message to send diff --git a/app/src/main/res/xml/miband_preferences.xml b/app/src/main/res/xml/miband_preferences.xml index 5a8b262f13..0503d6222e 100644 --- a/app/src/main/res/xml/miband_preferences.xml +++ b/app/src/main/res/xml/miband_preferences.xml @@ -84,6 +84,62 @@ android:key="mi_button_press_count_match_delay" android:summary="@string/mi2_prefs_button_press_count_match_delay_summary" android:title="@string/mi2_prefs_button_press_count_match_delay" /> + + + + + + + + + + + + + + + + + + + - \ No newline at end of file +