1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-07-21 22:31:16 +02:00

Mi Band 4: Add password support

This commit is contained in:
José Rebelo 2022-07-05 19:29:16 +01:00
parent 872ef44868
commit 9cb6403c04
11 changed files with 261 additions and 2 deletions

View File

@ -52,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.CalBlacklistActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureWorldClocks;
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
@ -214,6 +215,8 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
}
private void setChangeListener() {
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
final Prefs prefs = new Prefs(getPreferenceManager().getSharedPreferences());
String disconnectNotificationState = prefs.getString(PREF_DISCONNECT_NOTIFICATION, PREF_DO_NOT_DISTURB_OFF);
boolean disconnectNotificationScheduled = disconnectNotificationState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);
@ -731,6 +734,8 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
setInputTypeFor(DeviceSettingsPreferenceConst.PREF_RESERVER_REMINDERS_CALENDAR, InputType.TYPE_CLASS_NUMBER);
setInputTypeFor(DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD, InputType.TYPE_CLASS_NUMBER);
new PasswordCapabilityImpl().registerPreferences(getContext(), coordinator.getPasswordCapability(), this);
String deviceActionsFellSleepSelection = prefs.getString(PREF_DEVICE_ACTION_FELL_SLEEP_SELECTION, PREF_DEVICE_ACTION_SELECTION_OFF);
final Preference deviceActionsFellSleep = findPreference(PREF_DEVICE_ACTION_FELL_SLEEP_SELECTION);
final Preference deviceActionsFellSleepBroadcast = findPreference(PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST);
@ -748,7 +753,7 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
if (deviceActionsFellSleepBroadcast != null) {
deviceActionsFellSleepBroadcast.setEnabled(deviceActionsFellSleepSelectionBroadcast);
}
String deviceActionsWokeUpSelection = prefs.getString(PREF_DEVICE_ACTION_WOKE_UP_SELECTION, PREF_DEVICE_ACTION_SELECTION_OFF);
final Preference deviceActionsWokeUp = findPreference(PREF_DEVICE_ACTION_WOKE_UP_SELECTION);
final Preference deviceActionsWokeUpBroadcast = findPreference(PREF_DEVICE_ACTION_WOKE_UP_BROADCAST);
@ -766,7 +771,7 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
if (deviceActionsWokeUpBroadcast != null) {
deviceActionsWokeUpBroadcast.setEnabled(deviceActionsWokeUpSelectionBroadcast);
}
String deviceActionsStartNonWearSelection = prefs.getString(PREF_DEVICE_ACTION_START_NON_WEAR_SELECTION, PREF_DEVICE_ACTION_SELECTION_OFF);
final Preference deviceActionsStartNonWear = findPreference(PREF_DEVICE_ACTION_START_NON_WEAR_SELECTION);
final Preference deviceActionsStartNonWearBroadcast = findPreference(PREF_DEVICE_ACTION_START_NON_WEAR_BROADCAST);

View File

@ -0,0 +1,145 @@
/* Copyright (C) 2022 José Rebelo
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.capabilities.password;
import android.content.Context;
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
import android.text.TextWatcher;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.preference.EditTextPreference;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
public class PasswordCapabilityImpl {
public static final String PREF_PASSWORD = "pref_password";
public static final String PREF_PASSWORD_ENABLED = "pref_password_enabled";
public static enum Mode {
NONE,
NUMBERS_4_DIGITS_1_TO_4,
NUMBERS_6,
}
public void registerPreferences(final Context context, final Mode mode, final DeviceSpecificSettingsHandler handler) {
if (mode == Mode.NONE) {
return;
}
final EditTextPreference password = handler.findPreference(PREF_PASSWORD);
if (password == null) {
return;
}
handler.addPreferenceHandlerFor(PREF_PASSWORD);
handler.addPreferenceHandlerFor(PREF_PASSWORD_ENABLED);
switch (mode) {
case NUMBERS_6:
password.setSummary(R.string.prefs_password_6_digits_0_to_9_summary);
break;
case NUMBERS_4_DIGITS_1_TO_4:
password.setSummary(R.string.prefs_password_4_digits_1_to_4_summary);
break;
default:
break;
}
password.setOnBindEditTextListener(new EditTextPreference.OnBindEditTextListener() {
@Override
public void onBindEditText(@NonNull final EditText editText) {
final int expectedLength;
final List<InputFilter> inputFilters = new ArrayList<>();
switch (mode) {
case NUMBERS_6:
password.setSummary(R.string.prefs_password_6_digits_0_to_9_summary);
editText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
expectedLength = 6;
break;
case NUMBERS_4_DIGITS_1_TO_4:
password.setSummary(R.string.prefs_password_4_digits_1_to_4_summary);
editText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
expectedLength = 4;
inputFilters.add(new InputFilter_Digits_1to4());
break;
default:
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
expectedLength = -1;
break;
}
if (expectedLength != -1) {
inputFilters.add(new InputFilter.LengthFilter(expectedLength));
}
editText.setSelection(editText.getText().length());
editText.setFilters(inputFilters.toArray(new InputFilter[0]));
editText.addTextChangedListener(new ExpectedLengthTextWatcher(editText, expectedLength));
}
});
}
private static class InputFilter_Digits_1to4 implements InputFilter {
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
for (int i = start; i < end; i++) {
if (!Character.isDigit(source.charAt(i))) {
return "";
}
if (source.charAt(i) < '1' || source.charAt(i) > '4') {
return "";
}
}
return null;
}
}
private static class ExpectedLengthTextWatcher implements TextWatcher {
private final EditText editText;
private final int expectedLength;
private ExpectedLengthTextWatcher(final EditText editText, final int expectedLength) {
this.editText = editText;
this.expectedLength = expectedLength;
}
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
}
@Override
public void afterTextChanged(final Editable editable) {
editText.getRootView().findViewById(android.R.id.button1)
.setEnabled(expectedLength == -1 || editable.length() == expectedLength);
}
}
}

View File

@ -40,6 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
@ -315,4 +316,9 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
public boolean supportsPowerOff() {
return false;
}
@Override
public PasswordCapabilityImpl.Mode getPasswordCapability() {
return PasswordCapabilityImpl.Mode.NONE;
}
}

View File

@ -33,6 +33,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
@ -424,4 +425,6 @@ public interface DeviceCoordinator {
BatteryConfig[] getBatteryConfig();
boolean supportsPowerOff();
PasswordCapabilityImpl.Mode getPasswordCapability();
}

View File

@ -44,6 +44,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay;
@ -278,6 +279,16 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator {
return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ACTIVITY_MONITORING, false);
}
public static boolean getPasswordEnabled(String deviceAddress) throws IllegalArgumentException {
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
return prefs.getBoolean(PasswordCapabilityImpl.PREF_PASSWORD_ENABLED, false);
}
public static String getPassword(String deviceAddress) throws IllegalArgumentException {
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
return prefs.getString(PasswordCapabilityImpl.PREF_PASSWORD, null);
}
public static boolean getHeartrateAlert(String deviceAddress) throws IllegalArgumentException {
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
return prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ENABLED, false);

View File

@ -27,6 +27,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
@ -100,6 +101,7 @@ public class MiBand4Coordinator extends HuamiCoordinator {
R.xml.devicesettings_liftwrist_display,
R.xml.devicesettings_inactivity_dnd,
R.xml.devicesettings_swipeunlock,
R.xml.devicesettings_password,
R.xml.devicesettings_sync_calendar,
R.xml.devicesettings_reserve_reminders_calendar,
R.xml.devicesettings_expose_hr_thirdparty,
@ -139,4 +141,9 @@ public class MiBand4Coordinator extends HuamiCoordinator {
public int getBondingStyle() {
return BONDING_STYLE_REQUIRE_KEY;
}
@Override
public PasswordCapabilityImpl.Mode getPasswordCapability() {
return PasswordCapabilityImpl.Mode.NUMBERS_4_DIGITS_1_TO_4;
}
}

View File

@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
@ -96,6 +97,7 @@ public class MiBand6Coordinator extends HuamiCoordinator {
R.xml.devicesettings_liftwrist_display_sensitivity,
R.xml.devicesettings_inactivity_dnd,
R.xml.devicesettings_swipeunlock,
// TODO: test on the Mi Band 6 R.xml.devicesettings_password,
R.xml.devicesettings_sync_calendar,
R.xml.devicesettings_reserve_reminders_calendar,
R.xml.devicesettings_expose_hr_thirdparty,
@ -137,4 +139,10 @@ public class MiBand6Coordinator extends HuamiCoordinator {
public int getBondingStyle() {
return BONDING_STYLE_REQUIRE_KEY;
}
// TODO: Test on the Mi Band 6
// @Override
// public PasswordCapabilityImpl.Mode getPasswordCapability() {
// return PasswordCapabilityImpl.Mode.NUMBERS_6;
// }
}

View File

@ -71,6 +71,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
@ -666,6 +667,36 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport {
// not supported
}
private HuamiSupport setPassword(final TransactionBuilder builder) {
final boolean passwordEnabled = HuamiCoordinator.getPasswordEnabled(gbDevice.getAddress());
final String password = HuamiCoordinator.getPassword(gbDevice.getAddress());
LOG.info("Setting password: {}, {}", passwordEnabled, password);
if (password == null || password.isEmpty()) {
LOG.warn("Invalid password: {}", password);
return this;
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
baos.write(ENDPOINT_DISPLAY);
baos.write(0x21);
baos.write(0x00);
baos.write((byte) (passwordEnabled ? 0x01 : 0x00));
baos.write(password.getBytes());
baos.write(0x00);
} catch (final IOException e) {
LOG.error("Failed to build password command", e);
return this;
}
writeToConfiguration(builder, baos.toByteArray());
return this;
}
/**
* Part of device initialization process. Do not call manually.
*
@ -2728,6 +2759,10 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport {
case PREF_HEARTRATE_STRESS_MONITORING:
setHeartrateStressMonitoring(builder);
break;
case PasswordCapabilityImpl.PREF_PASSWORD:
case PasswordCapabilityImpl.PREF_PASSWORD_ENABLED:
setPassword(builder);
break;
}
builder.queue(getQueue());
} catch (IOException e) {
@ -3854,6 +3889,8 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport {
}
public void phase3Initialize(TransactionBuilder builder) {
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
LOG.info("phase3Initialize...");
setDateDisplay(builder);
setTimeFormat(builder);
@ -3877,6 +3914,9 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport {
setHeartrateMeasurementInterval(builder, HuamiCoordinator.getHeartRateMeasurementInterval(getDevice().getAddress()));
sendReminders(builder);
setWorldClocks(builder);
if (!PasswordCapabilityImpl.Mode.NONE.equals(coordinator.getPasswordCapability())) {
setPassword(builder);
}
requestAlarms(builder);
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#7E7E7E"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M2,17h20v2H2V17zM3.15,12.95L4,11.47l0.85,1.48l1.3,-0.75L5.3,10.72H7v-1.5H5.3l0.85,-1.47L4.85,7L4,8.47L3.15,7l-1.3,0.75L2.7,9.22H1v1.5h1.7L1.85,12.2L3.15,12.95zM9.85,12.2l1.3,0.75L12,11.47l0.85,1.48l1.3,-0.75l-0.85,-1.48H15v-1.5h-1.7l0.85,-1.47L12.85,7L12,8.47L11.15,7l-1.3,0.75l0.85,1.47H9v1.5h1.7L9.85,12.2zM23,9.22h-1.7l0.85,-1.47L20.85,7L20,8.47L19.15,7l-1.3,0.75l0.85,1.47H17v1.5h1.7l-0.85,1.48l1.3,0.75L20,11.47l0.85,1.48l1.3,-0.75l-0.85,-1.48H23V9.22z"/>
</vector>

View File

@ -718,6 +718,11 @@
<string name="mi2_prefs_inactivity_warnings_dnd_summary">Disable inactivity warnings for a time interval</string>
<string name="mi2_prefs_heart_rate_monitoring">Heart Rate Monitoring</string>
<string name="mi2_prefs_heart_rate_monitoring_summary">Configure heart rate monitoring</string>
<string name="prefs_password">Password</string>
<string name="prefs_password_summary">Lock the band with a password when removed from the wrist</string>
<string name="prefs_password_enabled">Password Enabled</string>
<string name="prefs_password_4_digits_1_to_4_summary">The password must have 4 digits, using numbers 1 to 4</string>
<string name="prefs_password_6_digits_0_to_9_summary">The password must have 6 digits, using only numbers</string>
<string name="mi2_prefs_heart_rate_monitoring_alerts_summary">Configure heart rate monitoring and alert thresholds</string>
<string name="mi2_prefs_do_not_disturb_start">Start time</string>
<string name="mi2_prefs_do_not_disturb_end">End time</string>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen
android:icon="@drawable/ic_password"
android:key="password_monitoring_key"
android:persistent="false"
android:summary="@string/prefs_password_summary"
android:title="@string/prefs_password">
<SwitchPreference
android:defaultValue="false"
android:icon="@drawable/ic_password"
android:key="pref_password_enabled"
android:summary="@string/prefs_password_summary"
android:title="@string/prefs_password_enabled" />
<EditTextPreference
android:dependency="pref_password_enabled"
android:icon="@drawable/ic_password"
android:inputType="numberPassword"
android:key="pref_password"
android:title="@string/prefs_password"/>
</PreferenceScreen>
</androidx.preference.PreferenceScreen>