diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index ff358c7e5..632d29984 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -43,7 +43,7 @@ public class GBDaoGenerator { public static void main(String[] args) throws Exception { - Schema schema = new Schema(31, MAIN_PACKAGE + ".entities"); + Schema schema = new Schema(32, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -79,6 +79,7 @@ public class GBDaoGenerator { addLefunSleepSample(schema, user, device); addSonySWR12Sample(schema, user, device); addBangleJSActivitySample(schema, user, device); + addCasioGBX100Sample(schema, user, device); addHybridHRActivitySample(schema, user, device); addCalendarSyncState(schema, device); @@ -420,6 +421,16 @@ public class GBDaoGenerator { return activitySample; } + private static Entity addCasioGBX100Sample(Schema schema, Entity user, Entity device) { + Entity activitySample = addEntity(schema, "CasioGBX100ActivitySample"); + activitySample.implementsSerializable(); + addCommonActivitySampleProperties("AbstractGBX100ActivitySample", activitySample, user, device); + activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE); + activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE); + activitySample.addIntProperty("calories").notNull(); + return activitySample; + } + private static Entity addLefunActivitySample(Schema schema, Entity user, Entity device) { Entity activitySample = addEntity(schema, "LefunActivitySample"); activitySample.implementsSerializable(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index 04ca55b86..de55385a1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -52,6 +52,11 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_LONGSIT_SWITCH_NOSHED = "screen_longsit_noshed"; public static final String PREF_DO_NOT_DISTURB_NOAUTO = "do_not_disturb_no_auto"; public static final String PREF_FIND_PHONE_ENABLED = "prefs_find_phone"; + public static final String PREF_AUTOLIGHT = "autolight"; + public static final String PREF_AUTOREMOVE_MESSAGE = "autoremove_message"; + public static final String PREF_OPERATING_SOUNDS = "operating_sounds"; + public static final String PREF_KEY_VIBRATION = "key_vibration"; + public static final String PREF_FAKE_RING_DURATION = "fake_ring_duration"; public static final String PREF_ANTILOST_ENABLED = "pref_antilost_enabled"; public static final String PREF_HYDRATION_SWITCH = "pref_hydration_switch"; 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 1b3969915..43bfca8c0 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 @@ -48,6 +48,8 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AMPM_ENABLED; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ANTILOST_ENABLED; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BT_CONNECTED_ADVERTISEMENT; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOLIGHT; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOREMOVE_MESSAGE; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_DOUBLE; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_LONG; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_SHORT; @@ -61,17 +63,20 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FAKE_RING_DURATION; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FIND_PHONE_ENABLED; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_FORCE_WHITE_COLOR; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_KEY_VIBRATION; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LEFUN_INTERFACE_LANGUAGE; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LONGSIT_PERIOD; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LONGSIT_SWITCH; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_OPERATING_SOUNDS; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_POWER_MODE; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SCREEN_ORIENTATION; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONYSWR12_LOW_VIBRATION; @@ -383,6 +388,11 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat { addPreferenceHandlerFor(PREF_LONGSIT_SWITCH); addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_NOAUTO); addPreferenceHandlerFor(PREF_FIND_PHONE_ENABLED); + addPreferenceHandlerFor(PREF_AUTOLIGHT); + addPreferenceHandlerFor(PREF_AUTOREMOVE_MESSAGE); + addPreferenceHandlerFor(PREF_KEY_VIBRATION); + addPreferenceHandlerFor(PREF_OPERATING_SOUNDS); + addPreferenceHandlerFor(PREF_FAKE_RING_DURATION); addPreferenceHandlerFor(PREF_ANTILOST_ENABLED); addPreferenceHandlerFor(PREF_HYDRATION_SWITCH); addPreferenceHandlerFor(PREF_HYDRATION_PERIOD); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioConstants.java index 2db060ac5..bfa58c94a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioConstants.java @@ -103,7 +103,7 @@ public final class CasioConstants { public static final byte CATEGORY_CONDITION = 12; public static final byte CATEGORY_EMAIL = 6; public static final byte CATEGORY_ENTERTAINMENT = 11; - public static final byte CATEGORY_HEATH_AND_FITNESS = 8; + public static final byte CATEGORY_HEALTH_AND_FITNESS = 8; public static final byte CATEGORY_INCOMING_CALL = 1; public static final byte CATEGORY_LOCATION = 10; public static final byte CATEGORY_MISSED_CALL = 2; @@ -129,9 +129,21 @@ public final class CasioConstants { OPTION_STEP_GOAL, OPTION_DISTANCE_GOAL, OPTION_ACTIVITY_GOAL, + OPTION_AUTOLIGHT, + OPTION_TIMEFORMAT, + OPTION_KEY_VIBRATION, + OPTION_OPERATING_SOUNDS, OPTION_ALL } + public static final int CASIO_CONVOY_DATATYPE_STEPS = 0x04; + public static final int CASIO_CONVOY_DATATYPE_CALORIES = 0x05; + + public static final int CASIO_FAKE_RING_SLEEP_DURATION = 3000; + public static final int CASIO_FAKE_RING_RETRIES = 10; + + public static final int CASIO_AUTOREMOVE_MESSAGE_DELAY = 10000; + public static Map characteristicToByte = new HashMap() { { put("CASIO_WATCH_NAME", (byte) 0x23); @@ -150,6 +162,10 @@ public final class CasioConstants { put("CASIO_SETTING_FOR_USER_PROFILE", (byte) 0x45); put("CASIO_SETTING_FOR_TARGET_VALUE", (byte) 0x43); put("ALERT_LEVEL", (byte) 0x0a); + put("CASIO_SETTING_FOR_ALM", (byte) 0x15); + put("CASIO_SETTING_FOR_ALM2", (byte) 0x16); + put("CASIO_SETTING_FOR_BASIC", (byte) 0x13); + put("CASIO_CURRENT_TIME_MANAGER", (byte) 0x39); } }; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioGBX100SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioGBX100SampleProvider.java new file mode 100644 index 000000000..7d0f36067 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/CasioGBX100SampleProvider.java @@ -0,0 +1,97 @@ +/* Copyright (C) 2018-2020 Cre3per, Daniele Gobbetti, Sebastian Kranz + + 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.devices.casio; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import de.greenrobot.dao.AbstractDao; +import de.greenrobot.dao.Property; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.CasioGBX100ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.entities.CasioGBX100ActivitySampleDao; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class CasioGBX100SampleProvider extends AbstractSampleProvider { + private static final Logger LOG = LoggerFactory.getLogger(CasioGBX100SampleProvider.class); + + public CasioGBX100SampleProvider(GBDevice device, DaoSession session) { + super(device, session); + } + + @Override + public int normalizeType(int rawType) { + return rawType; + } + + @Override + public int toRawActivityKind(int activityKind) { + return activityKind; + } + + @Override + public float normalizeIntensity(int rawIntensity) { + // The magic number 1500 is based on + // https://www.livestrong.com/article/474836-what-sport-burns-the-most-calories-per-hour/ + return (rawIntensity / 1500f); + } + + @Override + public CasioGBX100ActivitySample createActivitySample() { + return new CasioGBX100ActivitySample(); + } + + @Override + public AbstractDao getSampleDao() { + return getSession().getCasioGBX100ActivitySampleDao(); + } + + @Nullable + @Override + protected Property getRawKindSampleProperty() { + return CasioGBX100ActivitySampleDao.Properties.RawKind; + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return CasioGBX100ActivitySampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return CasioGBX100ActivitySampleDao.Properties.DeviceId; + } + + + @Override + public List getActivitySamples(int timestamp_from, int timestamp_to) { + return super.getActivitySamples(timestamp_from, timestamp_to); + } + + @Override + public List getAllActivitySamples(int timestamp_from, int timestamp_to) { + return super.getActivitySamples(timestamp_from, timestamp_to); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100DeviceCoordinator.java index dda42ba14..9ffeef57e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casio/gbx100/CasioGBX100DeviceCoordinator.java @@ -26,12 +26,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import androidx.annotation.NonNull; + +import de.greenrobot.dao.query.QueryBuilder; import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; -import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioGBX100SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.CasioGBX100ActivitySampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -97,17 +100,17 @@ public class CasioGBX100DeviceCoordinator extends AbstractDeviceCoordinator { @Override public boolean supportsActivityDataFetching() { - return false; + return true; } @Override public boolean supportsActivityTracking() { - return false; + return true; } @Override public SampleProvider getSampleProvider(GBDevice device, DaoSession session) { - return null; + return new CasioGBX100SampleProvider(device, session); } @Override @@ -147,14 +150,22 @@ public class CasioGBX100DeviceCoordinator extends AbstractDeviceCoordinator { @Override protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { - + Long deviceId = device.getId(); + QueryBuilder qb = session.getCasioGBX100ActivitySampleDao().queryBuilder(); + qb.where(CasioGBX100ActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities(); } @Override public int[] getSupportedDeviceSpecificSettings(GBDevice device) { return new int[]{ R.xml.devicesettings_find_phone, - R.xml.devicesettings_wearlocation + R.xml.devicesettings_wearlocation, + R.xml.devicesettings_timeformat, + R.xml.devicesettings_autolight, + R.xml.devicesettings_key_vibration, + R.xml.devicesettings_operating_sounds, + R.xml.devicesettings_fake_ring_duration, + R.xml.devicesettings_autoremove_message }; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioGBX100DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioGBX100DeviceSupport.java index 33006dbd1..fd0b6b09c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioGBX100DeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/CasioGBX100DeviceSupport.java @@ -32,6 +32,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; +import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -39,10 +40,17 @@ import java.util.concurrent.atomic.AtomicInteger; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioGBX100SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants; +import nodomain.freeyourgadget.gadgetbridge.entities.CasioGBX100ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.entities.Device; +import nodomain.freeyourgadget.gadgetbridge.entities.User; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; @@ -53,12 +61,20 @@ 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.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.operations.FetchStepCountDataOperation; 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 static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOLIGHT; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AUTOREMOVE_MESSAGE; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FAKE_RING_DURATION; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FIND_PHONE_ENABLED; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_KEY_VIBRATION; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_OPERATING_SOUNDS; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION; 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; @@ -72,9 +88,13 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen private boolean mFirstConnect = false; private boolean mGetConfigurationPending = false; - private ArrayList mSyncedNotificationIDs = new ArrayList<>(); - private int mLastCallId = 0; + private boolean mRingNotificationPending = false; + private final ArrayList mSyncedNotificationIDs = new ArrayList<>(); + private int mLastCallId = new AtomicInteger((int) (System.currentTimeMillis()/1000)).incrementAndGet(); + private int mFakeRingDurationCounter = 0; private final Handler mFindPhoneHandler = new Handler(); + private final Handler mFakeRingDurationHandler = new Handler(); + private final Handler mAutoRemoveMessageHandler = new Handler(); public CasioGBX100DeviceSupport() { super(LOG); @@ -106,12 +126,20 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen getDevice().setFirmwareVersion("N/A"); getDevice().setFirmwareVersion2("N/A"); - SharedPreferences preferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress()); - preferences.registerOnSharedPreferenceChangeListener(this); + + //preferences.registerOnSharedPreferenceChangeListener(this); SharedPreferences prefs = GBApplication.getPrefs().getPreferences(); prefs.registerOnSharedPreferenceChangeListener(this); + if(mFirstConnect) { + SharedPreferences preferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress()); + SharedPreferences.Editor editor = preferences.edit(); + + editor.putString("charts_tabs", "activity,activitylist,stepsweek"); + editor.apply(); + } + return builder; } @@ -128,6 +156,67 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen return super.onCharacteristicRead(gatt, characteristic, status); } + public CasioGBX100ActivitySample getSumWithinRange(int timestamp_from, int timestamp_to) { + int steps = 0; + int calories = 0; + try (DBHandler dbHandler = GBApplication.acquireDB()) { + + User user = DBHelper.getUser(dbHandler.getDaoSession()); + Device device = DBHelper.getDevice(this.getDevice(), dbHandler.getDaoSession()); + + CasioGBX100SampleProvider provider = new CasioGBX100SampleProvider(this.getDevice(), dbHandler.getDaoSession()); + List samples = provider.getActivitySamples(timestamp_from, timestamp_to); + for(CasioGBX100ActivitySample sample : samples) { + if(sample.getDevice().equals(device) && + sample.getUser().equals(user)) { + steps += sample.getSteps(); + calories += sample.getCalories(); + } + } + } catch (Exception e) { + LOG.error("Error fetching activity data."); + } + + CasioGBX100ActivitySample ret = new CasioGBX100ActivitySample(); + ret.setCalories(calories); + ret.setSteps(steps); + LOG.debug("Fetched for today: " + calories + " cals and " + steps + " steps."); + return ret; + } + + private void addGBActivitySamples(ArrayList samples) { + try (DBHandler dbHandler = GBApplication.acquireDB()) { + + User user = DBHelper.getUser(dbHandler.getDaoSession()); + Device device = DBHelper.getDevice(this.getDevice(), dbHandler.getDaoSession()); + + CasioGBX100SampleProvider provider = new CasioGBX100SampleProvider(this.getDevice(), dbHandler.getDaoSession()); + + for (CasioGBX100ActivitySample sample : samples) { + sample.setDevice(device); + sample.setUser(user); + sample.setProvider(provider); + + provider.addGBActivitySample(sample); + } + + } catch (Exception ex) { + // Why is this a toast? The user doesn't care about the error. + GB.toast(getContext(), "Error saving samples: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext()); + + LOG.error(ex.getMessage()); + } + } + + public void stepCountDataFetched(int totalCount, int totalCalories, ArrayList data) { + LOG.info("Got the following step count data: "); + LOG.info("Total Count: " + totalCount); + LOG.info("Total Calories: " + totalCalories); + + addGBActivitySamples(data); + } + @Override public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { @@ -144,7 +233,18 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen onReverseFindDevice(false); } return true; + } else if(data[0] == CasioConstants.characteristicToByte.get("CASIO_CURRENT_TIME_MANAGER")) { + if(data[1] == 0x00) { + try { + TransactionBuilder builder = performInitialized("writeCurrentTime"); + writeCurrentTime(builder); + builder.queue(getQueue()); + } catch (IOException e) { + LOG.warn("writing current time failed: " + e.getMessage()); + } + } } + } LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + String.format("0x%1x ...", data[0])); @@ -186,21 +286,22 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen arr[5] = (byte) 0x01; // Set to 0x00 to not vibrate/ring for this notification arr[6] = icon; // These bytes contain a timestamp, not yet decoded / implemented - /*arr[7] = (byte) 0x32; - arr[8] = (byte) 0x30; - arr[9] = (byte) 0x32; - arr[10] = (byte) 0x30; - arr[11] = (byte) 0x31; - arr[12] = (byte) 0x31; - arr[13] = (byte) 0x31; - arr[14] = (byte) 0x33; - arr[15] = (byte) 0x54; - arr[16] = (byte) 0x30; - arr[17] = (byte) 0x39; - arr[18] = (byte) 0x33; - arr[19] = (byte) 0x31; - arr[20] = (byte) 0x35; - arr[21] = (byte) 0x33;*/ + // ASCII Codes: + /*arr[7] = (byte) 0x32; // 2 + arr[8] = (byte) 0x30; // 0 + arr[9] = (byte) 0x32; // 2 + arr[10] = (byte) 0x30; // 0 + arr[11] = (byte) 0x31; // 1 + arr[12] = (byte) 0x31; // 1 + arr[13] = (byte) 0x31; // 1 + arr[14] = (byte) 0x33; // 3 + arr[15] = (byte) 0x54; // T + arr[16] = (byte) 0x30; // 0 + arr[17] = (byte) 0x39; // 9 + arr[18] = (byte) 0x33; // 3 + arr[19] = (byte) 0x31; // 1 + arr[20] = (byte) 0x35; // 5 + arr[21] = (byte) 0x33;*/// 3 byte[] copy = Arrays.copyOf(arr, arr.length + 2); copy[copy.length-2] = 0; copy[copy.length-1] = 0; @@ -247,8 +348,9 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen } @Override - public void onNotification(NotificationSpec notificationSpec) { + public void onNotification(final NotificationSpec notificationSpec) { byte icon; + boolean autoremove = false; switch (notificationSpec.type.getGenericType()) { case "generic_calendar": icon = CasioConstants.CATEGORY_SCHEDULE_AND_ALARM; @@ -256,13 +358,26 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen case "generic_email": icon = CasioConstants.CATEGORY_EMAIL; break; + case "generic_sms": + icon = CasioConstants.CATEGORY_SNS; + SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + autoremove = sharedPreferences.getBoolean(PREF_AUTOREMOVE_MESSAGE, false); + break; default: icon = CasioConstants.CATEGORY_SNS; break; } LOG.info("onNotification id=" + notificationSpec.getId()); showNotification(icon, notificationSpec.sender, notificationSpec.title, notificationSpec.body, notificationSpec.getId(), false); - mSyncedNotificationIDs.add(new Integer(notificationSpec.getId())); + mSyncedNotificationIDs.add(notificationSpec.getId()); + if(autoremove) { + mAutoRemoveMessageHandler.postDelayed(new Runnable() { + @Override + public void run() { + onDeleteNotification(notificationSpec.getId()); + } + }, CasioConstants.CASIO_AUTOREMOVE_MESSAGE_DELAY); + } // The watch only holds up to 10 notifications. However, the user might have deleted // some notifications in the meantime, so to be sure, we keep the last 100 IDs. if(mSyncedNotificationIDs.size() > 100) { @@ -273,7 +388,7 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen @Override public void onDeleteNotification(int id) { LOG.info("onDeleteNofication id=" + id); - Integer idInt = new Integer(id); + Integer idInt = id; if(mSyncedNotificationIDs.contains(idInt)) { showNotification(CasioConstants.CATEGORY_OTHER, null, null, null, id, true); mSyncedNotificationIDs.remove(idInt); @@ -300,7 +415,7 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen int iDuration; try { - iDuration = Integer.valueOf(duration); + iDuration = Integer.parseInt(duration); } catch (Exception ex) { LOG.warn(ex.getMessage()); iDuration = 60; @@ -331,7 +446,7 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen int year = cal.get(Calendar.YEAR); arr[0] = CasioConstants.characteristicToByte.get("CASIO_CURRENT_TIME"); - arr[1] = (byte)((year >>> 0) & 0xff); + arr[1] = (byte)(year & 0xff); arr[2] = (byte)((year >>> 8) & 0xff); arr[3] = (byte)(1 + cal.get(Calendar.MONTH)); arr[4] = (byte)cal.get(Calendar.DAY_OF_MONTH); @@ -358,7 +473,45 @@ public class CasioGBX100DeviceSupport extends AbstractBTLEDeviceSupport implemen @Override public void onSetAlarms(ArrayList alarms) { + int alarmOffset = 4; + byte[] data1 = new byte[5]; + byte[] data2 = new byte[17]; + if(!isConnected()) + return; + + data1[0] = CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_ALM"); + data2[0] = CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_ALM2"); + + for(int i=0; i { + private static final Logger LOG = LoggerFactory.getLogger(FetchStepCountDataOperation.class); + + private final CasioGBX100DeviceSupport support; + + public FetchStepCountDataOperation(CasioGBX100DeviceSupport support) { + super(support); + this.support = support; + } + + private void enableRequiredNotifications(boolean enable) { + try { + TransactionBuilder builder = performInitialized("enableRequiredNotifications"); + builder.setGattCallback(this); + builder.notify(getCharacteristic(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID), enable); + builder.notify(getCharacteristic(CasioConstants.CASIO_CONVOY_CHARACTERISTIC_UUID), enable); + builder.queue(getQueue()); + } catch(IOException e) { + LOG.info("Error enabling required notifications" + e.getMessage()); + } + } + + private void requestStepCountData() { + byte[] command = {0x00, 0x11, 0x00, 0x00, 0x00}; + + try { + TransactionBuilder builder = performInitialized("requestStepCountDate"); + builder.setGattCallback(this); + builder.write(getCharacteristic(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID), command); + builder.queue(getQueue()); + } catch(IOException e) { + LOG.info("Error requesting step count data: " + e.getMessage()); + } + } + + private void writeStepCountAck() { + byte[] command = {0x04, 0x11, 0x00, 0x00, 0x00}; + + try { + TransactionBuilder builder = performInitialized("writeStepCountAck"); + builder.setGattCallback(this); + builder.write(getCharacteristic(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID), command); + builder.queue(getQueue()); + } catch(IOException e) { + LOG.info("Error requesting step count data: " + e.getMessage()); + } + } + + @Override + protected void prePerform() throws IOException { + super.prePerform(); + getDevice().setBusyTask("FetchStepCountDataOperation starting..."); // mark as busy quickly to avoid interruptions from the outside + GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 0, getContext()); + } + + @Override + protected void doPerform() throws IOException { + enableRequiredNotifications(true); + requestStepCountData(); + } + + @Override + protected void operationFinished() { + LOG.info("SetConfigurationOperation finished"); + unsetBusy(); + GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), false, 100, getContext()); + + + operationStatus = OperationStatus.FINISHED; + if (getDevice() != null) { + try { + TransactionBuilder builder = performInitialized("finished 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()); + } + } + } + + @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_DATA_REQUEST_SP_CHARACTERISTIC_UUID)) { + int length = 0; + if (data.length > 3) { + length = (data[2] & 0xff) | ((data[3] & 0xff) << 8); + } + LOG.debug("Response is going to be " + length + " bytes long"); + GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 10, getContext()); + + return true; + } else if(characteristicUUID.equals(CasioConstants.CASIO_CONVOY_CHARACTERISTIC_UUID)) { + if(data.length < 18) { + LOG.info("Data length too short."); + } else { + for(int i=0; i stepCountData = new ArrayList<>(); + + int year = data[2]; + int month = data[3] - 1; // Zero-based + int day = data[4]; + int hour = data[5]; + int minute = data[6]; + + int stepCount = ((data[7] & 0xff) | ((data[8] & 0xff) << 8) | ((data[9] & 0xff) << 16) | ((data[10] & 0xff) << 24)); + // it reports 0xfffffffe if no steps have been recorded + if(stepCount == 0xfffffffe) + stepCount = 0; + + int calories = ((data[11] & 0xff) | ((data[12] & 0xff) << 8)); + if(calories == 0xfffe) + calories = 0; + int yearOfBirth = ((data[13] & 0xff) | ((data[14] & 0xff) << 8)); + int monthOfBirth = data[15]; + int dayOfBirth = data[16]; + LOG.debug("Current step count value: " + stepCount); + LOG.debug("Current calories: " + calories); + // data[17]: + // 0x01 probably means finished. + // 0x00 probably means more data. + + // Set timestamps for retrieving recorded data for the current day + + cal.set(year + 2000, month, day, hour, 30, 0); + int ts_to = (int)(cal.getTimeInMillis() / 1000); + cal.set(year + 2000, month, day, 0, 0, 0); + int ts_from = (int)(cal.getTimeInMillis() / 1000); + + CasioGBX100ActivitySample sum = support.getSumWithinRange(ts_from, ts_to); + + int caloriesToday = sum.getCalories(); + int stepsToday = sum.getSteps(); + + // Set timestamp to currently fetched data for fetching historic data + cal.set(year + 2000, month, day, hour, 30, 0); + + if(data[17] == 0x00 && data.length > 18) { + LOG.info("We got historic step count data."); + int index = 18; + boolean inPacket = false; + int packetIndex = 0; + int packetLength = 0; + int type = 0; + while(index < data.length) { + if(!inPacket) { + type = data[index]; + packetLength = ((data[index + 1] & 0xff) | ((data[index + 2] & 0xff) << 8)); + packetIndex = 0; + inPacket = true; + index = index + 3; + LOG.debug("Decoding packet with type: " + type + " and length: " + packetLength); + } + int count = ((data[index] & 0xff) | ((data[index + 1] & 0xff) << 8)); + if(count == 0xfffe) + count = 0; + LOG.debug("Got count " + count); + + index = index+2; + if(index >= data.length) { + LOG.debug("End of packet."); + } + if(type == CasioConstants.CASIO_CONVOY_DATATYPE_STEPS) { + cal.add(Calendar.HOUR, -1); + int ts = (int)(cal.getTimeInMillis() / 1000); + stepCountData.add(new CasioGBX100ActivitySample()); + stepCountData.get(packetIndex/2).setSteps(count); + stepCountData.get(packetIndex/2).setTimestamp(ts); + if(count > 0) { + stepCountData.get(packetIndex / 2).setRawKind(ActivityKind.TYPE_ACTIVITY); + } else { + stepCountData.get(packetIndex / 2).setRawKind(ActivityKind.TYPE_NOT_MEASURED); + } + if(ts > ts_from && ts < ts_to) { + stepsToday += count; + } + } else if(type == CasioConstants.CASIO_CONVOY_DATATYPE_CALORIES) { + if(stepCountData.get(packetIndex/2).getSteps() > 0) { + // The last packet might contain an invalid calory count + // of 255, but only if the steps are also invalid. + stepCountData.get(packetIndex / 2).setCalories(count); + int ts = stepCountData.get(packetIndex / 2).getTimestamp(); + if (ts > ts_from && ts < ts_to) { + caloriesToday += count; + } + } + } + packetIndex = packetIndex + 2; + if(packetIndex >= packetLength) + inPacket = false; + } + } + + // This generates an artificial "now" timestamp for the current + // activity based on the existing data. This timestamp will be overwritten + // by the next fetch operation with the actual value. + int steps = stepCount - stepsToday; + int cals = calories - caloriesToday; + + // For a yet unknown reason, the sum calculated by the watch is sometimes lower than + // the sum calculated by us. I suspect it is just refreshed at a later time!? + + if(steps > 0 && cals > 0) { + + cal.set(year + 2000, month, day, hour, 30, 0); + int ts = (int) (cal.getTimeInMillis() / 1000); + + LOG.debug("Artificial timestamp: " + cals + " calories and " + steps + " steps"); + CasioGBX100ActivitySample sample = new CasioGBX100ActivitySample(); + sample.setSteps(steps); + sample.setCalories(cals); + sample.setTimestamp(ts); + if (steps > 0) + sample.setRawKind(ActivityKind.TYPE_ACTIVITY); + else + sample.setRawKind(ActivityKind.TYPE_NOT_MEASURED); + stepCountData.add(0, sample); + } + + support.stepCountDataFetched(stepCount, calories, stepCountData); + } + GB.updateTransferNotification(null, getContext().getString(R.string.busy_task_fetch_activity_data), true, 80, getContext()); + + writeStepCountAck(); + return true; + } else { + LOG.info("Unhandled characteristic changed: " + characteristicUUID); + return super.onCharacteristicChanged(gatt, characteristic); + } + } + + @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_DATA_REQUEST_SP_CHARACTERISTIC_UUID)) { + if(data[0] == 0x00) { + LOG.debug("Request sent successfully"); + } else if(data[0] == 0x04) { + LOG.debug("Read step count operation finished"); + enableRequiredNotifications(false); + operationFinished(); + } + return true; + } + return super.onCharacteristicWrite(gatt, characteristic, status); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/operations/GetConfigurationOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/operations/GetConfigurationOperation.java index f1507f33d..e6a527088 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/operations/GetConfigurationOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/operations/GetConfigurationOperation.java @@ -4,25 +4,23 @@ 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_AUTOLIGHT; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_KEY_VIBRATION; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_OPERATING_SOUNDS; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION; public class GetConfigurationOperation extends AbstractBTLEOperation { @@ -36,6 +34,12 @@ public class GetConfigurationOperation extends AbstractBTLEOperation> 8) & 0xff); } + if(Arrays.equals(oldData, data)) { + LOG.info("No configuration update required"); + requestBasicSettings(); + } else { + // Basic settings will be requested 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; + } else if(data[0] == CasioConstants.characteristicToByte.get("CASIO_SETTING_FOR_BASIC")) { + SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + GBPrefs gbPrefs = new GBPrefs(new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()))); + + String timeformat = gbPrefs.getTimeFormat(); + + if(timeformat.equals(getContext().getString(R.string.p_timeformat_24h))) { + data[1] |= 0x01; + } else { + data[1] &= ~0x01; + } + + boolean autolight = sharedPreferences.getBoolean(PREF_AUTOLIGHT, false); + if(autolight) { + data[1] &= ~0x04; + } else { + data[1] |= 0x04; + } + + boolean key_vibration = sharedPreferences.getBoolean(PREF_KEY_VIBRATION, true); + if (key_vibration) { + data[10] = 1; + } else { + data[10] = 0; + } + + boolean operating_sounds = sharedPreferences.getBoolean(PREF_OPERATING_SOUNDS, false); + if(operating_sounds) { + data[1] &= ~0x02; + } else { + data[1] |= 0x02; + } + if(Arrays.equals(oldData, data)) { LOG.info("No configuration update required"); operationFinished(); @@ -186,6 +242,7 @@ public class SetConfigurationOperation extends AbstractBTLEOperation%d hours %d hours + Automatic light + Key Vibration + Operating Sounds + Fake continuous ringing + Automatically remove SMS notifications diff --git a/app/src/main/res/xml/devicesettings_autolight.xml b/app/src/main/res/xml/devicesettings_autolight.xml new file mode 100644 index 000000000..3fe564e68 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_autolight.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/devicesettings_autoremove_message.xml b/app/src/main/res/xml/devicesettings_autoremove_message.xml new file mode 100644 index 000000000..4be7a9800 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_autoremove_message.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/devicesettings_fake_ring_duration.xml b/app/src/main/res/xml/devicesettings_fake_ring_duration.xml new file mode 100644 index 000000000..e2cc4fa76 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_fake_ring_duration.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/devicesettings_key_vibration.xml b/app/src/main/res/xml/devicesettings_key_vibration.xml new file mode 100644 index 000000000..4e5b28fbf --- /dev/null +++ b/app/src/main/res/xml/devicesettings_key_vibration.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/devicesettings_operating_sounds.xml b/app/src/main/res/xml/devicesettings_operating_sounds.xml new file mode 100644 index 000000000..9749e9a8b --- /dev/null +++ b/app/src/main/res/xml/devicesettings_operating_sounds.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file