diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
index d1f6060b9..dd4ff86a3 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
@@ -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);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/password/PasswordCapabilityImpl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/password/PasswordCapabilityImpl.java
new file mode 100644
index 000000000..adaea4027
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/password/PasswordCapabilityImpl.java
@@ -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 . */
+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 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);
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
index dc631621c..6c427e723 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
@@ -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;
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
index aee7b5ad0..0e37517cb 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
@@ -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();
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java
index cc517c788..2fe91576b 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java
@@ -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);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4Coordinator.java
index 83e9730a3..4a4661cd4 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband4/MiBand4Coordinator.java
@@ -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;
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6Coordinator.java
index 26f7e703f..77efb590e 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband6/MiBand6Coordinator.java
@@ -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;
+ // }
}
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 bbd8fb20f..99144d2f0 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
@@ -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);
}
diff --git a/app/src/main/res/drawable/ic_password.xml b/app/src/main/res/drawable/ic_password.xml
new file mode 100644
index 000000000..072f0ee04
--- /dev/null
+++ b/app/src/main/res/drawable/ic_password.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d2c28eaff..3805107fe 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -718,6 +718,11 @@
Disable inactivity warnings for a time interval
Heart Rate Monitoring
Configure heart rate monitoring
+ Password
+ Lock the band with a password when removed from the wrist
+ Password Enabled
+ The password must have 4 digits, using numbers 1 to 4
+ The password must have 6 digits, using only numbers
Configure heart rate monitoring and alert thresholds
Start time
End time
diff --git a/app/src/main/res/xml/devicesettings_password.xml b/app/src/main/res/xml/devicesettings_password.xml
new file mode 100644
index 000000000..e8bface25
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_password.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+