Skip to content
This repository has been archived by the owner on Mar 25, 2024. It is now read-only.

Initial implementation for Mi Band 2 'custom menu' #1453

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

<uses-permission android:name="cyanogenmod.permission.ACCESS_WEATHER_MANAGER" />
<uses-permission android:name="cyanogenmod.permission.READ_WEATHER" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<HuamiSupport> deviceInfoProfile;
private final IntentListener mListener = new IntentListener() {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand All @@ -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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>. */
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);
}
}
12 changes: 12 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,18 @@
<string name="prefs_disconnect_notification">Disconnect notification</string>
<string name="mi2_prefs_button_actions">Button actions</string>
<string name="mi2_prefs_button_actions_summary">Specify actions on Mi Band 2 button press</string>
<string name="mi2_prefs_button_action_menu">Custom Menu</string>
<string name="mi2_prefs_button_action_menu_summary">Create a custom menu on Mi Band 2</string>
<string name="mi2_prefs_menu_elements">Menu Elements</string>
<string name="mi2_prefs_menu_elements_summary">The elements of your menu (label1:message1;label2:message2;...)</string>
<string name="mi2_prefs_menu_forward">Move forward</string>
<string name="mi2_prefs_menu_forward_summary">How many taps to move forward in the menu</string>
<string name="mi2_prefs_menu_backward">Move backward</string>
<string name="mi2_prefs_menu_backward_summary">How many taps to move backward in the menu</string>
<string name="mi2_prefs_menu_validate">Validate current option</string>
<string name="mi2_prefs_menu_validate_summary">How many taps to confirm selected menu option</string>
<string name="mi2_prefs_menu_wakelock">Enable WakeLock</string>
<string name="mi2_prefs_menu_wakelock_summary">Enable if you have issues when phone sleeps. WARNING : this may reduce your battery life.</string>
<string name="mi2_prefs_button_press_count">Button press count</string>
<string name="mi2_prefs_button_press_count_summary">Number of button presses to trigger message broadcast</string>
<string name="mi2_prefs_button_press_broadcast">Broadcast message to send</string>
Expand Down
Loading