1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-27 11:05:49 +01:00

Add support for synchronizing profile settings

This commit is contained in:
Andreas Böhler 2020-11-20 21:28:53 +01:00 committed by Gitea
parent 2ef9128cf8
commit 8fd0e1f13f
6 changed files with 533 additions and 41 deletions

View File

@ -120,6 +120,18 @@ public final class CasioConstants {
MODEL_CASIO_GBX100
}
public enum ConfigurationOption {
OPTION_GENDER,
OPTION_WEIGHT,
OPTION_HEIGHT,
OPTION_WRIST,
OPTION_BIRTHDAY,
OPTION_STEP_GOAL,
OPTION_DISTANCE_GOAL,
OPTION_ACTIVITY_GOAL,
OPTION_ALL
}
public static Map<String, Byte> characteristicToByte = new HashMap<String, Byte>() {
{
put("CASIO_WATCH_NAME", (byte) 0x23);
@ -135,6 +147,9 @@ public final class CasioConstants {
put("CASIO_DST_SETTING", (byte) 0x1e);
put("CASIO_SERVICE_DISCOVERY_MANAGER", (byte) 0x47);
put("CASIO_CURRENT_TIME", (byte) 0x09);
put("CASIO_SETTING_FOR_USER_PROFILE", (byte) 0x45);
put("CASIO_SETTING_FOR_TARGET_VALUE", (byte) 0x43);
put("ALERT_LEVEL", (byte) 0x0a);
}
};
}

View File

@ -117,7 +117,7 @@ public class CasioGBX100DeviceCoordinator extends AbstractDeviceCoordinator {
@Override
public int getAlarmSlotCount() {
return 0; // 4 regular and one snooze
return 4;
}
@Override
@ -149,4 +149,12 @@ public class CasioGBX100DeviceCoordinator extends AbstractDeviceCoordinator {
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
}
@Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return new int[]{
R.xml.devicesettings_find_phone,
R.xml.devicesettings_wearlocation
};
}
}

View File

@ -17,12 +17,11 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.casio;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Handler;
import android.widget.Toast;
import org.slf4j.Logger;
@ -37,10 +36,12 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
@ -51,24 +52,29 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.ServerTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.operations.GetConfigurationOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.operations.InitOperationGBX100;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.operations.SetConfigurationOperation;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport {
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FIND_PHONE_ENABLED;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_ACTIVETIME_MINUTES;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_DISTANCE_METERS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GENDER;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_HEIGHT_CM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_STEPS_GOAL;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_WEIGHT_KG;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_YEAR_OF_BIRTH;
public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final Logger LOG = LoggerFactory.getLogger(CasioGBX100DeviceSupport.class);
private final ArrayList<BluetoothGattCharacteristic> mCasioCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
private MusicSpec mBufferMusicSpec = null;
private MusicStateSpec mBufferMusicStateSpec = null;
private BluetoothGatt mBtGatt = null;
private boolean mFirstConnect = false;
private boolean mGetConfigurationPending = false;
private ArrayList<Integer> mSyncedNotificationIDs = new ArrayList<>();
private int mLastCallId = 0;
private final Handler mFindPhoneHandler = new Handler();
public CasioGBX100DeviceSupport() {
super(LOG);
@ -82,15 +88,10 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport {
return connect();
}
public void setInitialized(TransactionBuilder builder) {
public void setInitialized() {
mFirstConnect = false;
builder.add(new SetDeviceStateAction(gbDevice, GBDevice.State.INITIALIZED, getContext()));
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt) {
mBtGatt = gatt;
super.onServicesDiscovered(gatt);
gbDevice.setState(GBDevice.State.INITIALIZED);
gbDevice.sendDeviceUpdateIntent(getContext());
}
@Override
@ -105,6 +106,12 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport {
getDevice().setFirmwareVersion("N/A");
getDevice().setFirmwareVersion2("N/A");
SharedPreferences preferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress());
preferences.registerOnSharedPreferenceChangeListener(this);
SharedPreferences prefs = GBApplication.getPrefs().getPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
return builder;
}
@ -124,13 +131,22 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport {
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
boolean handled = false;
UUID characteristicUUID = characteristic.getUuid();
byte[] data = characteristic.getValue();
if (data.length == 0)
return true;
if (characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID)) {
if(data[0] == CasioConstants.characteristicToByte.get("ALERT_LEVEL")) {
if(data[1] == 0x02) {
onReverseFindDevice(true);
} else {
onReverseFindDevice(false);
}
return true;
}
}
LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0]));
return super.onCharacteristicChanged(gatt, characteristic);
}
@ -140,6 +156,14 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport {
return true;
}
public void syncProfile() {
try {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_ALL).perform();
} catch (IOException e) {
GB.toast(getContext(), "Sending Casio configuration failed", Toast.LENGTH_SHORT, GB.ERROR, e);
}
}
private void showNotification(byte icon, String sender, String title, String message, int id, boolean delete) {
byte[] titleBytes = new byte[0];
if(title != null)
@ -256,6 +280,51 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport {
}
}
private void onReverseFindDevice(boolean start) {
if (start) {
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
String findPhone = sharedPreferences.getString(PREF_FIND_PHONE_ENABLED, getContext().getString(R.string.p_off));
if(findPhone.equals(getContext().getString(R.string.p_off)))
return;
GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
evaluateGBDeviceEvent(findPhoneEvent);
if(!findPhone.equals(getContext().getString(R.string.p_on))) {
String duration = sharedPreferences.getString(MakibesHR3Constants.PREF_FIND_PHONE_DURATION, "0");
try {
int iDuration;
try {
iDuration = Integer.valueOf(duration);
} catch (Exception ex) {
LOG.warn(ex.getMessage());
iDuration = 60;
}
if(iDuration > 0) {
this.mFindPhoneHandler.postDelayed(new Runnable() {
@Override
public void run() {
onReverseFindDevice(false);
}
}, iDuration * 1000);
}
} catch (Exception e) {
LOG.error("Unexpected exception in MiBand2Coordinator.getTime: " + e.getMessage());
}
}
} else {
// Always send stop, ignore preferences.
GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
evaluateGBDeviceEvent(findPhoneEvent);
}
}
public void writeCurrentTime(TransactionBuilder builder) {
byte[] arr = new byte[11];
Calendar cal = Calendar.getInstance();
@ -306,11 +375,14 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport {
@Override
public void onSetCallState(CallSpec callSpec) {
final AtomicInteger c = new AtomicInteger((int) (System.currentTimeMillis()/1000));
switch (callSpec.command) {
case CallSpec.CALL_INCOMING:
showNotification(CasioConstants.CATEGORY_INCOMING_CALL, "Phone", callSpec.name, callSpec.number, c.incrementAndGet(), false);
final AtomicInteger c = new AtomicInteger((int) (System.currentTimeMillis()/1000));
mLastCallId = c.incrementAndGet();
showNotification(CasioConstants.CATEGORY_INCOMING_CALL, "Phone", callSpec.name, callSpec.number, mLastCallId, false);
break;
case CallSpec.CALL_END:
showNotification(CasioConstants.CATEGORY_INCOMING_CALL, null, null, null, mLastCallId, true);
default:
LOG.info("not sending CallSpec since only CALL_INCOMING is handled");
break;
@ -423,12 +495,26 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport {
@Override
public void onSendConfiguration(String config) {
LOG.info("onSendConfiguration" + config);
}
public void onGetConfigurationFinished() {
mGetConfigurationPending = false;
}
@Override
public void onReadConfiguration(String config) {
LOG.info("onReadConfiguration" + config);
// This is called upon pairing to retrieve the current watch settings, if any
if(config == null) {
try {
mGetConfigurationPending = true;
new GetConfigurationOperation(this, true).perform();
} catch (IOException e) {
mGetConfigurationPending = false;
GB.toast(getContext(), "Reading Casio configuration failed", Toast.LENGTH_SHORT, GB.ERROR, e);
}
}
}
@Override
@ -440,4 +526,44 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport {
public void onSendWeather(WeatherSpec weatherSpec) {
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
LOG.debug(key + " changed");
if (!this.isConnected()) {
LOG.debug("ignoring change, we're disconnected");
return;
}
if(mGetConfigurationPending) {
LOG.debug("Preferences are being fetched right now");
return;
}
try {
if (key.equals(DeviceSettingsPreferenceConst.PREF_WEARLOCATION)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_WRIST).perform();
} else if(key.equals(PREF_USER_STEPS_GOAL)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_STEP_GOAL).perform();
} else if(key.equals(PREF_USER_ACTIVETIME_MINUTES)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_ACTIVITY_GOAL).perform();
} else if(key.equals(PREF_USER_DISTANCE_METERS)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_DISTANCE_GOAL).perform();
} else if(key.equals(PREF_USER_GENDER)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_GENDER).perform();
} else if(key.equals(PREF_USER_HEIGHT_CM)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_HEIGHT).perform();
} else if(key.equals(PREF_USER_WEIGHT_KG)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_WEIGHT).perform();
} else if(key.equals(PREF_USER_YEAR_OF_BIRTH)) {
new SetConfigurationOperation(this, CasioConstants.ConfigurationOption.OPTION_BIRTHDAY).perform();
} else if (key.equals(PREF_FIND_PHONE_ENABLED) ||
key.equals(MakibesHR3Constants.PREF_FIND_PHONE_DURATION)) {
// No action, we check the shared preferences when the device tries to ring the phone.
} else {
return;
}
} catch (IOException e) {
LOG.info("Error sending configuration change to watch");
}
}
}

View File

@ -0,0 +1,119 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.operations;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.SharedPreferences;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.UUID;
import java.util.prefs.Preferences;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsFragment;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.PlainAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.CasioGBX100DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
import nodomain.freeyourgadget.gadgetbridge.util.BcdUtil;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION;
public class GetConfigurationOperation extends AbstractBTLEOperation<CasioGBX100DeviceSupport> {
private static final Logger LOG = LoggerFactory.getLogger(GetConfigurationOperation.class);
private final CasioGBX100DeviceSupport support;
private final boolean mFirstConnect;
public GetConfigurationOperation(CasioGBX100DeviceSupport support, boolean firstconnect) {
super(support);
this.support = support;
this.mFirstConnect = firstconnect;
}
@Override
protected void doPerform() throws IOException {
byte[] command = new byte[1];
command[0] = CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_USER_PROFILE");
TransactionBuilder builder = performInitialized("getConfiguration");
builder.setGattCallback(this);
support.writeAllFeaturesRequest(builder, command);
builder.queue(getQueue());
}
@Override
protected void operationFinished() {
operationStatus = OperationStatus.FINISHED;
if (getDevice() != null) {
try {
TransactionBuilder builder = performInitialized("finishe operation");
builder.wait(0);
builder.setGattCallback(null); // unset ourselves from being the queue's gatt callback
builder.queue(getQueue());
} catch (IOException ex) {
LOG.info("Error resetting Gatt callback: " + ex.getMessage());
}
}
support.onGetConfigurationFinished();
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
UUID characteristicUUID = characteristic.getUuid();
byte[] data = characteristic.getValue();
if (data.length == 0)
return true;
if (characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID)) {
if(data[0] == CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_USER_PROFILE")) {
boolean female = ((data[1] & 0x01) == 0x01) ;
boolean right = ((data[1] & 0x02) == 0x02);
byte[] compData = new byte[data.length];
for(int i=0; i<data.length; i++) {
compData[i] = (byte)(~data[i]);
}
int height = BcdUtil.fromBcd8(compData[2]) + BcdUtil.fromBcd8(compData[3]) * 100;
int weight = BcdUtil.fromBcd8(compData[4]) + BcdUtil.fromBcd8(compData[5]) * 100;
int year = BcdUtil.fromBcd8(compData[6]) + BcdUtil.fromBcd8(compData[7]) * 100;
int month = BcdUtil.fromBcd8(compData[8]);
int day = BcdUtil.fromBcd8(compData[9]) - 1;
// Store only the device-specific settings on first-connect
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
SharedPreferences.Editor editor = prefs.edit();
editor.putString(PREF_WEARLOCATION, right ? "right" : "left");
editor.apply();
LOG.info("GetConfigurationOperation finished");
operationFinished();
// Retrieve all settings from the watch, this overwrites the profile
// on first connect, overwrite the watch settings
if(!mFirstConnect) {
} else {
support.syncProfile();
}
}
return true;
} else {
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
return super.onCharacteristicChanged(gatt, characteristic);
}
}
@Override
public boolean onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
return super.onCharacteristicRead(gatt, characteristic, status);
}
}

View File

@ -260,16 +260,6 @@ public class InitOperationGBX100 extends AbstractBTLEOperation<CasioGBX100Device
}
}
private void setInitialized() {
try {
TransactionBuilder builder = createTransactionBuilder("setInitialized");
support.setInitialized(builder);
support.performImmediately(builder);
} catch(IOException e) {
LOG.error("Error setting device to initialized: " + e.getMessage());
}
}
private void enableAllFeatures(TransactionBuilder builder, boolean enable) {
builder.notify(getCharacteristic(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID), enable);
}
@ -365,12 +355,18 @@ public class InitOperationGBX100 extends AbstractBTLEOperation<CasioGBX100Device
if(mFirstConnect)
writeAllFeaturesInit();
else
setInitialized();
support.setInitialized();
}
} else if(data[0] == 0x3d) {
LOG.info("Init operation done.");
// Finally, we set the state to initialized here!
setInitialized();
support.setInitialized();
if(mFirstConnect) {
support.onReadConfiguration(null);
} else {
// on first connect, this is called by onReadConfiguration
support.syncProfile();
}
}
return true;
} else {

View File

@ -0,0 +1,228 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.operations;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.SharedPreferences;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.CasioGBX100DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
import nodomain.freeyourgadget.gadgetbridge.util.BcdUtil;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.GENDER_MALE;
public class SetConfigurationOperation extends AbstractBTLEOperation<CasioGBX100DeviceSupport> {
private static final Logger LOG = LoggerFactory.getLogger(GetConfigurationOperation.class);
private final CasioGBX100DeviceSupport support;
private final CasioConstants.ConfigurationOption option;
public SetConfigurationOperation(CasioGBX100DeviceSupport support, CasioConstants.ConfigurationOption option) {
super(support);
this.support = support;
this.option = option;
}
@Override
protected void doPerform() throws IOException {
byte[] command = new byte[1];
command[0] = CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_USER_PROFILE");
TransactionBuilder builder = performInitialized("getConfiguration");
builder.setGattCallback(this);
support.writeAllFeaturesRequest(builder, command);
builder.queue(getQueue());
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
UUID characteristicUUID = characteristic.getUuid();
byte[] data = characteristic.getValue();
if (data.length == 0)
return true;
if (characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID)) {
byte[] oldData = new byte[data.length];
System.arraycopy(data, 0, oldData, 0, data.length);
if (data[0] == CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_USER_PROFILE")) {
ActivityUser user = new ActivityUser();
boolean all = (option == CasioConstants.ConfigurationOption.OPTION_ALL);
if (option == CasioConstants.ConfigurationOption.OPTION_GENDER || all) {
if (user.getGender() == GENDER_MALE) {
data[1] = (byte) (data[1] & ~0x01);
} else {
data[1] = (byte) (data[1] | 0x01);
}
}
for(int i=2; i<data.length; i++) {
data[i] = (byte)~data[i];
}
if (option == CasioConstants.ConfigurationOption.OPTION_HEIGHT || all) {
int height = user.getHeightCm();
data[2] = BcdUtil.toBcd8(height % 100);
data[3] = BcdUtil.toBcd8((height - (height % 100)) / 100);
}
if (option == CasioConstants.ConfigurationOption.OPTION_WEIGHT || all) {
int weight = user.getWeightKg();
data[4] = BcdUtil.toBcd8(weight % 100);
data[5] = BcdUtil.toBcd8((weight - (weight % 100)) / 100);
}
if (option == CasioConstants.ConfigurationOption.OPTION_WRIST || all) {
String location = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getString(PREF_WEARLOCATION, "left");
if (location == "right") {
data[1] = (byte) (data[1] | 0x02);
} else {
data[1] = (byte) (data[1] & ~0x02);
}
}
if(option == CasioConstants.ConfigurationOption.OPTION_BIRTHDAY || all) {
int year = user.getYearOfBirth();
// Month and Day are not configured in Gadgetbridge!
int month = 1;
int day = 1;
data[6] = BcdUtil.toBcd8(year % 100);
data[7] = BcdUtil.toBcd8((year - (year % 100)) / 100);
data[8] = BcdUtil.toBcd8(month);
data[9] = BcdUtil.toBcd8(day + 1);
}
for(int i=2; i<data.length; i++) {
data[i] = (byte)~data[i];
}
if(Arrays.equals(oldData, data)) {
LOG.info("No configuration update required");
requestTargetSettings();
} else {
// Target settings will be requested in write callback
try {
TransactionBuilder builder = performInitialized("setConfiguration");
builder.setGattCallback(this);
support.writeAllFeatures(builder, data);
builder.queue(getQueue());
} catch (IOException e) {
LOG.info("Error writing configuration to Casio watch");
}
}
return true;
} else if (data[0] == CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_TARGET_VALUE")) {
ActivityUser user = new ActivityUser();
boolean all = (option == CasioConstants.ConfigurationOption.OPTION_ALL);
if(option == CasioConstants.ConfigurationOption.OPTION_STEP_GOAL || all) {
int steps = user.getStepsGoal();
data[1] = (byte)(steps & 0xff);
data[2] = (byte)((steps >> 8) & 0xff);
}
if(option == CasioConstants.ConfigurationOption.OPTION_DISTANCE_GOAL || all) {
// The watch requires a monthly goal, so we multiply that with 30
// and divide it by 100 because the value is set in 100m units
int distance = user.getDistanceMeters() * 30;
distance = distance / 100;
data[6] = (byte)(distance & 0xff);
data[7] = (byte)((distance >> 8) & 0xff);
}
if(option == CasioConstants.ConfigurationOption.OPTION_ACTIVITY_GOAL || all) {
// The watch requires a monthly goal, so we multiply that with 30
int time = user.getActiveTimeMinutes() * 30;
data[9] = (byte)(time & 0xff);
data[10] = (byte)((time >> 8) & 0xff);
}
if(Arrays.equals(oldData, data)) {
LOG.info("No configuration update required");
operationFinished();
} else {
// Operation will be finished in Gatt callback
try {
TransactionBuilder builder = performInitialized("setConfiguration");
builder.setGattCallback(this);
support.writeAllFeatures(builder, data);
builder.queue(getQueue());
} catch (IOException e) {
LOG.info("Error writing configuration to Casio watch");
}
}
return true;
}
}
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
return super.onCharacteristicChanged(gatt, characteristic);
}
@Override
protected void operationFinished() {
LOG.info("SetConfigurationOperation finished");
operationStatus = OperationStatus.FINISHED;
if (getDevice() != null) {
unsetBusy();
try {
TransactionBuilder builder = performInitialized("finishe operation");
builder.setGattCallback(null); // unset ourselves from being the queue's gatt callback
builder.wait(0);
builder.queue(getQueue());
} catch (IOException ex) {
LOG.info("Error resetting Gatt callback: " + ex.getMessage());
}
}
}
private void requestTargetSettings() {
byte[] command = new byte[1];
command[0] = CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_TARGET_VALUE");
try {
TransactionBuilder builder = performInitialized("getConfiguration");
builder.setGattCallback(this);
support.writeAllFeaturesRequest(builder, command);
builder.queue(getQueue());
} catch(IOException e) {
LOG.info("Error requesting Casio configuration");
}
}
@Override
public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
UUID characteristicUUID = characteristic.getUuid();
byte[] data = characteristic.getValue();
if (data.length == 0)
return true;
if (characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID)) {
if(data[0] == CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_USER_PROFILE")) {
requestTargetSettings();
return true;
}
if(data[0] == CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_TARGET_VALUE")) {
operationFinished();
return true;
}
}
return super.onCharacteristicWrite(gatt, characteristic, status);
}
}