From c7b4f295a1fa1504425f19b3be35116327888639 Mon Sep 17 00:00:00 2001 From: cpfeiffer Date: Sat, 20 Jun 2015 23:22:22 +0200 Subject: [PATCH] Support for vibration profiles, configurable for notifications Configurable for sms, k9, incoming calls, pebble messages, generic notifications. Color is unfortunately not configurable so far, see #37 Closes #29 Currently provided profiles are - staccato - short - medium - long - waterdrop - ring - alarm clock --- .../btle/AbortTransactionAction.java | 30 ++++ .../btle/CheckInitializedAction.java | 10 +- .../gadgetbridge/miband/MiBandConst.java | 13 +- .../miband/MiBandPreferencesActivity.java | 10 ++ .../gadgetbridge/miband/MiBandSupport.java | 86 +++++++++-- .../gadgetbridge/miband/VibrationProfile.java | 69 +++++++++ app/src/main/res/values/arrays.xml | 29 ++++ app/src/main/res/values/strings.xml | 21 +++ app/src/main/res/values/values.xml | 12 ++ app/src/main/res/xml/miband_preferences.xml | 133 ++++++++++++++---- app/src/main/res/xml/preferences.xml | 2 + 11 files changed, 374 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/AbortTransactionAction.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/VibrationProfile.java create mode 100644 app/src/main/res/values/values.xml diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/AbortTransactionAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/AbortTransactionAction.java new file mode 100644 index 000000000..f0f312796 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/AbortTransactionAction.java @@ -0,0 +1,30 @@ +package nodomain.freeyourgadget.gadgetbridge.btle; + +import android.bluetooth.BluetoothGatt; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.GBDevice; + +/** + * A special action that checks for an abort-condition, and if met, the currently + * executing transaction will be aborted by returning false. + */ +public abstract class AbortTransactionAction extends PlainAction { + private static final Logger LOG = LoggerFactory.getLogger(AbortTransactionAction.class); + + public AbortTransactionAction() { + } + + @Override + public boolean run(BluetoothGatt gatt) { + if (shouldAbort()) { + LOG.info("Aborting transaction because abort criteria met."); + return false; + } + return true; + } + + protected abstract boolean shouldAbort(); +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/CheckInitializedAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/CheckInitializedAction.java index 3ecd7bb13..58f57ff25 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/CheckInitializedAction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/CheckInitializedAction.java @@ -12,7 +12,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBDevice; * sequence (transaction). It will abort the entire initialization sequence * by returning false, when the device is already initialized. */ -public class CheckInitializedAction extends PlainAction { +public class CheckInitializedAction extends AbortTransactionAction { private static final Logger LOG = LoggerFactory.getLogger(CheckInitializedAction.class); private final GBDevice device; @@ -22,11 +22,11 @@ public class CheckInitializedAction extends PlainAction { } @Override - public boolean run(BluetoothGatt gatt) { - boolean continueWithOtherInitActions = !device.isInitialized(); - if (!continueWithOtherInitActions) { + protected boolean shouldAbort() { + boolean abort = device.isInitialized(); + if (abort) { LOG.info("Aborting device initialization, because already initialized: " + device); } - return continueWithOtherInitActions; + return abort; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandConst.java index 3e7e71c59..37e0d57f2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandConst.java @@ -5,6 +5,8 @@ import android.content.SharedPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import nodomain.freeyourgadget.gadgetbridge.R; + public final class MiBandConst { private static final Logger LOG = LoggerFactory.getLogger(MiBandConst.class); @@ -13,9 +15,11 @@ public final class MiBandConst { public static final String PREF_USER_GENDER = "mi_user_gender"; public static final String PREF_USER_HEIGHT_CM = "mi_user_height_cm"; public static final String PREF_USER_WEIGHT_KG = "mi_user_weight_kg"; + public static final String PREF_MIBAND_WEARSIDE = "mi_wearside"; public static final String PREF_MIBAND_ADDRESS = "development_miaddr"; // FIXME: should be prefixed mi_ public static final String ORIGIN_SMS = "sms"; + public static final String ORIGIN_INCOMING_CALL = "incoming_call"; public static final String ORIGIN_K9MAIL = "k9mail"; public static final String ORIGIN_PEBBLEMSG = "pebblemsg"; public static final String ORIGIN_GENERIC = "generic"; @@ -32,10 +36,16 @@ public final class MiBandConst { } } + public static String getNotificationPrefStringValue(String pref, String origin, SharedPreferences prefs, String defaultValue) { + String key = getNotificationPrefKey(pref, origin); + return prefs.getString(key, defaultValue); + } + public static final String getNotificationPrefKey(String pref, String origin) { return new StringBuilder(pref).append('_').append(origin).toString(); } + public static final String VIBRATION_PROFILE = "mi_vibration_profile"; public static final String VIBRATION_COUNT = "mi_vibration_count"; public static final String VIBRATION_DURATION = "mi_vibration_duration"; public static final String VIBRATION_PAUSE = "mi_vibration_pause"; @@ -45,7 +55,8 @@ public final class MiBandConst { public static final String FLASH_COLOUR = "mi_flash_colour"; public static final String FLASH_ORIGINAL_COLOUR = "mi_flash_original_colour"; - public static final int DEFAULT_VALUE_VIBRATION_COUNT = 6; + public static final String DEFAULT_VALUE_VIBRATION_PROFILE = "short"; + public static final int DEFAULT_VALUE_VIBRATION_COUNT = 3; public static final int DEFAULT_VALUE_VIBRATION_DURATION = 500; // ms public static final int DEFAULT_VALUE_VIBRATION_PAUSE = 500; // ms public static final int DEFAULT_VALUE_FLASH_COUNT = 10; // ms diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandPreferencesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandPreferencesActivity.java index 6def19aff..a8e03ac4d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandPreferencesActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandPreferencesActivity.java @@ -10,16 +10,19 @@ import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivity; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.ORIGIN_GENERIC; +import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.ORIGIN_INCOMING_CALL; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.ORIGIN_K9MAIL; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.ORIGIN_PEBBLEMSG; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.ORIGIN_SMS; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.PREF_MIBAND_ADDRESS; +import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.PREF_MIBAND_WEARSIDE; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.PREF_USER_ALIAS; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.PREF_USER_GENDER; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.PREF_USER_HEIGHT_CM; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.PREF_USER_WEIGHT_KG; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.PREF_USER_YEAR_OF_BIRTH; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.VIBRATION_COUNT; +import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.VIBRATION_PROFILE; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.getNotificationPrefKey; public class MiBandPreferencesActivity extends AbstractSettingsActivity { @@ -51,10 +54,17 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity { PREF_USER_GENDER, PREF_USER_HEIGHT_CM, PREF_USER_WEIGHT_KG, + PREF_MIBAND_WEARSIDE, PREF_MIBAND_ADDRESS, + getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_SMS), getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_SMS), + getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_INCOMING_CALL), + getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_INCOMING_CALL), + getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_K9MAIL), getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_K9MAIL), + getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_PEBBLEMSG), getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_PEBBLEMSG), + getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_GENERIC), getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_GENERIC), }; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java index 575e29ba8..b0dd21a6c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java @@ -22,11 +22,14 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBCommand; import nodomain.freeyourgadget.gadgetbridge.GBDevice.State; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.btle.AbortTransactionAction; import nodomain.freeyourgadget.gadgetbridge.btle.AbstractBTLEDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.btle.BtLEAction; import nodomain.freeyourgadget.gadgetbridge.btle.SetDeviceBusyAction; import nodomain.freeyourgadget.gadgetbridge.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler; +import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PROFILE; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_FLASH_COLOUR; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_FLASH_COUNT; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_FLASH_DURATION; @@ -44,7 +47,9 @@ import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.ORIGIN_SMS import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.VIBRATION_COUNT; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.VIBRATION_DURATION; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.VIBRATION_PAUSE; +import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.VIBRATION_PROFILE; import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.getNotificationPrefIntValue; +import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.getNotificationPrefStringValue; public class MiBandSupport extends AbstractBTLEDeviceSupport { @@ -63,6 +68,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { private GregorianCalendar activityDataTimestampProgress = null; //same as above, but remains untouched for the ack message private GregorianCalendar activityDataTimestampToAck = null; + private volatile boolean telephoneRinging; public MiBandSupport() { @@ -135,6 +141,42 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { builder.write(characteristic, getDefaultNotification()).queue(getQueue()); } + /** + * Sends a custom notification to the Mi Band. + * @param vibrationProfile specifies how and how often the Band shall vibrate. + * @param flashTimes + * @param flashColour + * @param originalColour + * @param flashDuration + * @param extraAction an extra action to be executed after every vibration and flash sequence. Allows to abort the repetition, for example. + * @param builder + */ + private void sendCustomNotification(VibrationProfile vibrationProfile, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) { + BluetoothGattCharacteristic controlPoint = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT); + for (byte i = 0; i < vibrationProfile.getRepeat(); i++) { + int[] onOffSequence = vibrationProfile.getOnOffSequence(); + for (int j = 0; j < onOffSequence.length; j++) { + int on = onOffSequence[j]; + on = Math.min(500, on); // longer than 500ms is not possible + builder.write(controlPoint, startVibrate); + builder.wait(on); + builder.write(controlPoint, stopVibrate); + + if (++j < onOffSequence.length) { + int off = Math.max(onOffSequence[j], 25); // wait at least 25ms + builder.wait(off); + } + + if (extraAction != null) { + builder.add(extraAction); + } + } + } + + LOG.info("Sending notification to MiBand: " + controlPoint); + builder.queue(getQueue()); + } + private void sendCustomNotification(int vibrateDuration, int vibrateTimes, int pause, int flashTimes, int flashColour, int originalColour, long flashDuration, TransactionBuilder builder) { BluetoothGattCharacteristic controlPoint = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT); int vDuration = Math.min(500, vibrateDuration); // longer than 500ms is not possible @@ -221,20 +263,22 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { // } // } - private void performPreferredNotification(String task, String notificationOrigin) { + private void performPreferredNotification(String task, String notificationOrigin, BtLEAction extraAction) { try { TransactionBuilder builder = performInitialized(task); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); int vibrateDuration = getPreferredVibrateDuration(notificationOrigin, prefs); - int vibrateTimes = getPreferredVibrateCount(notificationOrigin, prefs); int vibratePause = getPreferredVibratePause(notificationOrigin, prefs); + int vibrateTimes = getPreferredVibrateCount(notificationOrigin, prefs); + VibrationProfile profile = getPreferredVibrateProfile(notificationOrigin, prefs, vibrateTimes); int flashTimes = getPreferredFlashCount(notificationOrigin, prefs); int flashColour = getPreferredFlashColour(notificationOrigin, prefs); int originalColour = getPreferredOriginalColour(notificationOrigin, prefs); int flashDuration = getPreferredFlashDuration(notificationOrigin, prefs); - sendCustomNotification(vibrateDuration, vibrateTimes, vibratePause, flashTimes, flashColour, originalColour, flashDuration, builder); + sendCustomNotification(profile, flashTimes, flashColour, originalColour, flashDuration, extraAction, builder); +// sendCustomNotification(vibrateDuration, vibrateTimes, vibratePause, flashTimes, flashColour, originalColour, flashDuration, builder); } catch (IOException ex) { LOG.error("Unable to send notification to MI device", ex); } @@ -268,20 +312,24 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { return getNotificationPrefIntValue(VIBRATION_DURATION, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_DURATION); } + private VibrationProfile getPreferredVibrateProfile(String notificationOrigin, SharedPreferences prefs, int repeat) { + String profileId = getNotificationPrefStringValue(VIBRATION_PROFILE, notificationOrigin, prefs, DEFAULT_VALUE_VIBRATION_PROFILE); + return VibrationProfile.getProfile(profileId, (byte) repeat); + } + @Override public void onSMS(String from, String body) { -// performCustomNotification("sms received", 500, 3, 2000, 0, 0, 0, 0); - performPreferredNotification("sms received", ORIGIN_SMS); + performPreferredNotification("sms received", ORIGIN_SMS, null); } @Override public void onEmail(String from, String subject, String body) { - performPreferredNotification("email received", ORIGIN_K9MAIL); + performPreferredNotification("email received", ORIGIN_K9MAIL, null); } @Override public void onGenericNotification(String title, String details) { - performPreferredNotification("generic notification received", ORIGIN_GENERIC); + performPreferredNotification("generic notification received", ORIGIN_GENERIC, null); } @Override @@ -328,10 +376,24 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { @Override public void onSetCallState(String number, String name, GBCommand command) { if (GBCommand.CALL_INCOMING.equals(command)) { - performDefaultNotification("incoming call"); + telephoneRinging = true; + AbortTransactionAction abortAction = new AbortTransactionAction() { + @Override + protected boolean shouldAbort() { + return !isTelephoneRinging(); + } + }; + performPreferredNotification("incoming call", MiBandConst.ORIGIN_INCOMING_CALL, abortAction); + } else if (GBCommand.CALL_START.equals(command) || GBCommand.CALL_END.equals(command)) { + telephoneRinging = false; } } + private boolean isTelephoneRinging() { + // don't synchronize, this is not really important + return telephoneRinging; + } + @Override public void onSetMusicInfo(String artist, String album, String track) { // not supported @@ -556,8 +618,12 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { unsetBusy(); } } - for (byte b : value) { - LOG.info("handleControlPoint GOT DATA:" + String.format("0x%8x", b)); + if (value != null) { + for (byte b : value) { + LOG.info("handleControlPoint GOT DATA:" + String.format("0x%8x", b)); + } + } else { + LOG.warn("handleControlPoint GOT null"); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/VibrationProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/VibrationProfile.java new file mode 100644 index 000000000..7144f80ca --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/VibrationProfile.java @@ -0,0 +1,69 @@ +package nodomain.freeyourgadget.gadgetbridge.miband; + +import android.content.Context; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; + +public class VibrationProfile { + public static final Context CONTEXT = GBApplication.getContext(); + public static final String ID_STACCATO = CONTEXT.getString(R.string.p_staccato); + public static final String ID_SHORT = CONTEXT.getString(R.string.p_short); + public static final String ID_MEDIUM = CONTEXT.getString(R.string.p_medium); + public static final String ID_LONG = CONTEXT.getString(R.string.p_long); + public static final String ID_WATERDROP = CONTEXT.getString(R.string.p_waterdrop); + public static final String ID_RING = CONTEXT.getString(R.string.p_ring); + public static final String ID_ALARM_CLOCK = CONTEXT.getString(R.string.p_alarm_clock); + + public static VibrationProfile getProfile(String id, byte repeat) { + if (ID_STACCATO.equals(id)) { + return new VibrationProfile(id, new int[]{100, 0}, repeat); + } + if (ID_SHORT.equals(id)) { + return new VibrationProfile(id, new int[] {200, 200}, repeat); + } + if (ID_LONG.equals(id)) { + return new VibrationProfile(id, new int[] {500, 1000}, repeat); + } + if (ID_WATERDROP.equals(id)) { + return new VibrationProfile(id, new int[] {100, 1500}, repeat); + } + if (ID_RING.equals(id)) { + return new VibrationProfile(id, new int[]{300, 200, 600, 2000}, repeat); + } + if (ID_ALARM_CLOCK.equals(id)) { + return new VibrationProfile(id, new int[] {30, 35, 30, 35, 30, 35, 30, 800}, repeat); + } + // medium + return new VibrationProfile(id, new int[] {300, 600}, repeat); + } + + private final String id; + + private int[] onOffSequence; + private byte repeat; // 1-10 + + /** + * Creates a new profile instance. + * @param id the ID, used as preference key. + * @param onOffSequence a sequence of alternating on and off durations, in milliseconds + * @param repeat how ofoften the sequence shall be repeated + */ + public VibrationProfile(String id, int[] onOffSequence, byte repeat) { + this.id = id; + this.repeat = repeat; + this.onOffSequence = onOffSequence; + } + + public String getId() { + return id; + } + + public int[] getOnOffSequence() { + return onOffSequence; + } + + public byte getRepeat() { + return repeat; + } +} diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 7d88bcb66..88b4f5f34 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -21,4 +21,33 @@ female other + + @string/left + @string/right + + + left + right + + + + @string/vibration_profile_staccato + @string/vibration_profile_short + @string/vibration_profile_medium + @string/vibration_profile_long + @string/vibration_profile_waterdrop + @string/vibration_profile_ring + @string/vibration_profile_alarm_clock + + + + @string/p_staccato + @string/p_short + @string/p_medium + @string/p_long + @string/p_waterdrop + @string/p_ring + @string/p_alarm_clock + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 088b793c0..23b6a6d9f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -28,7 +28,9 @@ Sync time to device when connecting and when time or timezone changes on Android Notifications + Repetitions SMS + Incoming Calls K9-Mail Pebble Messages Support for applications which send Notifications to the Pebble via Intent. Can be used for Conversations. @@ -91,6 +93,8 @@ male female other + left + right No valid user data given, using dummy user data for now. When your Mi Band vibrates and blinks, tap it a few times in a row. Install @@ -112,5 +116,22 @@ Fetch Activity Data Disconnect From %1$s to %2$s + Wearing left or right? + Vibration Profile + + Staccato + Short + Medium + Long + Waterdrop + Ring + Alarm Clock + Vibration + SMS Notification + Vibration Settings + Generic Notification + Pebble Notification + K9 Mail Notification + Incoming Call Notification diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml new file mode 100644 index 000000000..a89f911ca --- /dev/null +++ b/app/src/main/res/values/values.xml @@ -0,0 +1,12 @@ + + + + staccato + short + medium + long + waterdrop + ring + alarm_clock + + diff --git a/app/src/main/res/xml/miband_preferences.xml b/app/src/main/res/xml/miband_preferences.xml index 4a77cb90a..1ac586b22 100644 --- a/app/src/main/res/xml/miband_preferences.xml +++ b/app/src/main/res/xml/miband_preferences.xml @@ -34,35 +34,118 @@ android:maxLength="3" android:title="@string/miband_prefs_weight_kg" /> + - - - - + android:title="@string/pref_header_vibration_settings"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +