2021-01-10 23:37:09 +01:00
/ * Copyright ( C ) 2015 - 2021 Andreas Shimokawa , Carsten Pfeiffer , Christian
Fischer , Daniele Gobbetti , Dmitry Markin , JohnnySun , José Rebelo , Julien
Pivotto , Kasha , Michal Novotny , Petr Vaněk , Sebastian Kranz , Sergey Trofimov ,
Steffen Liebergeld , Taavi Eomäe , Zhong Jianxin
2017-03-10 14:53:19 +01:00
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/>. */
2018-08-01 22:56:01 +02:00
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami ;
2016-07-25 00:00:22 +02:00
import android.bluetooth.BluetoothGatt ;
import android.bluetooth.BluetoothGattCharacteristic ;
import android.content.Context ;
import android.content.Intent ;
2020-05-12 22:42:19 +02:00
import android.content.SharedPreferences ;
2019-10-31 14:28:24 +01:00
import android.content.pm.ApplicationInfo ;
import android.content.pm.PackageManager ;
2022-06-04 22:20:28 +02:00
import android.location.Location ;
2022-05-05 15:14:15 +02:00
import android.media.AudioManager ;
2016-07-25 00:00:22 +02:00
import android.net.Uri ;
import android.widget.Toast ;
2019-05-20 16:36:06 +02:00
import androidx.localbroadcastmanager.content.LocalBroadcastManager ;
2020-09-30 18:16:25 +02:00
import net.e175.klaus.solarpositioning.DeltaT ;
import net.e175.klaus.solarpositioning.SPA ;
2016-11-24 21:58:32 +01:00
import org.apache.commons.lang3.ArrayUtils ;
2016-07-25 00:00:22 +02:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
2022-09-28 07:51:10 +02:00
import org.threeten.bp.Instant ;
import org.threeten.bp.ZoneId ;
import org.threeten.bp.zone.ZoneOffsetTransition ;
import org.threeten.bp.zone.ZoneRules ;
2016-07-25 00:00:22 +02:00
2022-05-09 20:47:08 +02:00
import java.io.ByteArrayOutputStream ;
2016-07-25 00:00:22 +02:00
import java.io.IOException ;
2018-08-06 23:11:40 +02:00
import java.nio.ByteBuffer ;
import java.nio.ByteOrder ;
2022-05-09 20:47:08 +02:00
import java.nio.charset.StandardCharsets ;
2016-07-25 00:00:22 +02:00
import java.util.ArrayList ;
2020-05-12 22:42:19 +02:00
import java.util.Arrays ;
2016-07-25 00:00:22 +02:00
import java.util.Calendar ;
2022-10-22 21:53:45 +02:00
import java.util.Collections ;
2017-07-15 15:01:07 +02:00
import java.util.Date ;
2016-07-25 00:00:22 +02:00
import java.util.GregorianCalendar ;
2020-05-12 22:42:19 +02:00
import java.util.HashSet ;
2023-05-21 00:34:36 +02:00
import java.util.LinkedList ;
2016-07-25 00:00:22 +02:00
import java.util.List ;
2019-05-20 16:36:06 +02:00
import java.util.Locale ;
2020-10-20 20:12:08 +02:00
import java.util.Map ;
2023-05-21 00:34:36 +02:00
import java.util.Queue ;
2017-07-09 16:17:13 +02:00
import java.util.Set ;
2019-08-25 09:55:23 +02:00
import java.util.SimpleTimeZone ;
2022-05-09 20:47:08 +02:00
import java.util.TimeZone ;
2017-09-10 21:11:50 +02:00
import java.util.Timer ;
import java.util.TimerTask ;
2016-07-25 00:00:22 +02:00
import java.util.UUID ;
2016-12-01 22:49:58 +01:00
import java.util.concurrent.TimeUnit ;
2016-07-25 00:00:22 +02:00
2019-08-25 09:55:23 +02:00
import cyanogenmod.weather.util.WeatherUtils ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.GBApplication ;
2017-03-20 22:41:54 +01:00
import nodomain.freeyourgadget.gadgetbridge.Logging ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.R ;
2017-10-02 22:23:17 +02:00
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity ;
2022-08-18 23:03:28 +02:00
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst ;
2022-07-05 20:29:16 +02:00
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl ;
2016-11-29 23:22:36 +01:00
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler ;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo ;
2017-10-21 22:50:28 +02:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl ;
2018-01-19 23:10:08 +01:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone ;
2018-08-02 22:35:02 +02:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl ;
2022-08-18 23:03:28 +02:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo ;
2018-08-07 12:44:00 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator ;
2016-11-29 23:22:36 +01:00
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider ;
2018-03-23 23:27:03 +01:00
import nodomain.freeyourgadget.gadgetbridge.devices.huami.ActivateDisplayOnLift ;
2022-05-09 14:57:44 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.huami.ActivateDisplayOnLiftSensitivity ;
2019-02-13 13:06:42 +01:00
import nodomain.freeyourgadget.gadgetbridge.devices.huami.DisconnectNotificationSetting ;
2023-06-10 18:19:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Coordinator ;
2022-08-18 23:03:28 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service ;
2018-07-17 00:29:36 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst ;
2017-10-24 22:01:25 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator ;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper ;
2018-08-02 10:55:30 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService ;
2019-08-25 09:55:23 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiWeatherConditions ;
2018-01-13 18:46:21 +01:00
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService ;
2019-05-22 00:42:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3Coordinator ;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3.MiBand3Service ;
2016-11-18 21:14:04 +01:00
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay ;
2017-07-15 15:01:07 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb ;
2016-11-29 23:22:36 +01:00
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2SampleProvider ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst ;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator ;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService ;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile ;
2016-11-29 23:22:36 +01:00
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession ;
import nodomain.freeyourgadget.gadgetbridge.entities.Device ;
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample ;
import nodomain.freeyourgadget.gadgetbridge.entities.User ;
2022-06-04 22:20:28 +02:00
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationManager ;
2022-05-22 18:11:45 +02:00
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State ;
2022-09-18 12:04:50 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind ;
2016-11-29 23:22:36 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample ;
2017-03-11 11:29:50 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.Alarm ;
2023-05-20 21:44:18 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes ;
2023-05-21 00:34:36 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.AbstractFetchOperation ;
2023-05-27 19:59:12 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchHeartRateManualOperation ;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchHeartRateMaxOperation ;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchHeartRateRestingOperation ;
2023-05-27 20:02:01 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchPaiOperation ;
2023-05-27 20:03:43 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSleepRespiratoryRateOperation ;
2023-05-21 00:34:36 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSpo2NormalOperation ;
2023-05-20 21:44:18 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSportsSummaryOperation ;
2023-05-21 00:34:36 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchStressAutoOperation ;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchStressManualOperation ;
2023-05-20 21:44:18 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.HuamiFetchDebugLogsOperation ;
2023-05-06 22:02:54 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsCannedMessagesService ;
2022-06-16 23:28:17 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent ;
import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec ;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec ;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService ;
2019-05-20 16:36:06 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec ;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec ;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec ;
2016-10-11 21:18:43 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType ;
2021-12-04 16:55:09 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.Reminder ;
2019-08-25 09:55:23 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.Weather ;
2016-12-31 15:56:05 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec ;
2022-05-09 20:47:08 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport ;
2016-08-17 00:53:16 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction ;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic ;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService ;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder ;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction ;
2019-05-20 16:36:06 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ConditionalWriteAction ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction ;
2018-08-18 00:39:14 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener ;
2017-03-02 00:27:54 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory ;
2019-10-31 14:28:24 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile ;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile ;
2017-03-02 00:27:54 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification ;
2018-11-05 23:27:29 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.actions.StopNotificationAction ;
2018-08-01 22:56:01 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2NotificationStrategy ;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2TextNotificationStrategy ;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchActivityOperation ;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.InitOperation ;
2021-08-16 12:36:14 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.InitOperation2021 ;
2018-08-01 22:56:01 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation ;
2018-07-17 00:29:36 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy ;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.RealtimeSamplesSupport ;
2018-12-16 16:05:13 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol ;
2019-01-07 01:10:57 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils ;
2018-08-07 12:44:00 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.GB ;
2020-01-04 23:40:50 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs ;
2017-03-02 00:27:54 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.Prefs ;
2019-10-31 14:28:24 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils ;
2017-03-15 00:26:39 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.Version ;
2016-07-25 00:00:22 +02:00
2022-03-07 17:42:46 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT ;
2020-02-04 10:04:01 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALLOW_HIGH_MTU ;
2020-12-18 10:56:42 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BT_CONNECTED_ADVERTISEMENT ;
2020-02-04 10:04:01 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT ;
2022-05-09 14:57:44 +02:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_SENSITIVITY ;
2022-03-07 17:42:46 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_START ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_END ;
2022-03-07 17:52:50 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISCONNECT_NOTIFICATION ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISCONNECT_NOTIFICATION_START ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISCONNECT_NOTIFICATION_END ;
2022-03-06 23:48:47 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_START ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_END ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_LIFT_WRIST ;
2022-05-16 18:42:02 +02:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ACTIVITY_MONITORING ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_ENABLED ;
2022-08-18 23:03:28 +02:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD ;
2022-05-16 18:42:02 +02:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HEARTRATE_STRESS_MONITORING ;
2022-08-09 14:30:21 +02:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_ENABLE ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_END ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HOURLY_CHIME_START ;
2022-03-07 15:51:54 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_START ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_END ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_START ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_END ;
2020-05-05 00:34:59 +02:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE ;
2020-02-04 10:04:01 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_RESERVER_ALARMS_CALENDAR ;
2021-12-04 16:55:09 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_RESERVER_REMINDERS_CALENDAR ;
2021-03-24 20:02:48 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SOUNDS ;
2020-02-24 14:19:06 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR ;
2020-02-04 10:04:01 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT ;
2022-05-14 22:08:32 +02:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_NOTIFICATION ;
2020-02-04 10:04:01 +01:00
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION ;
2022-08-18 23:03:28 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service.WORKOUT_GPS_FLAG_POSITION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service.WORKOUT_GPS_FLAG_STATUS ;
2020-12-18 10:56:42 +01:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_BUTTON_ACTION_SELECTION_BROADCAST ;
2022-02-19 16:04:48 +01:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_START ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_STOP ;
2022-05-09 17:18:06 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_TOGGLE ;
2020-12-18 10:56:42 +01:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_BUTTON_ACTION_SELECTION_OFF ;
2020-08-16 22:07:55 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_FELL_SLEEP_SELECTION ;
2020-08-28 15:38:18 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_SELECTION_OFF ;
2020-08-16 22:07:55 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_START_NON_WEAR_BROADCAST ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_START_NON_WEAR_SELECTION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_WOKE_UP_BROADCAST ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_WOKE_UP_SELECTION ;
2022-05-14 15:20:28 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_ALARM ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_APP_ALERTS ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_EVENT_REMINDER ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_FIND_BAND ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_GOAL_NOTIFICATION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_IDLE_ALERTS ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_INCOMING_CALL ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_INCOMING_SMS ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX ;
2022-10-22 21:53:45 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_SCHEDULE ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_TODO_LIST ;
2022-05-14 15:20:28 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_ALARM ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_APP_ALERTS ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_EVENT_REMINDER ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_FIND_BAND ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_GOAL_NOTIFICATION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_IDLE_ALERTS ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_INCOMING_CALL ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_INCOMING_SMS ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX ;
2022-10-22 21:53:45 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_SCHEDULE ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_TODO_LIST ;
2022-05-14 15:20:28 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_ALARM ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_APP_ALERTS ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_EVENT_REMINDER ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_FIND_BAND ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_GOAL_NOTIFICATION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_IDLE_ALERTS ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_INCOMING_CALL ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_INCOMING_SMS ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_PREFIX ;
2022-10-22 21:53:45 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_SCHEDULE ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_TODO_LIST ;
2022-05-15 18:52:01 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.COMMAND_ALARMS ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.COMMAND_ALARMS_WITH_TIMES ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.COMMAND_GPS_VERSION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.COMMAND_WORKOUT_ACTIVITY_TYPES ;
2020-11-07 10:19:28 +01:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.DISPLAY_ITEM_BIT_CLOCK ;
2022-05-16 18:42:02 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY ;
2020-11-07 10:19:28 +01:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY_ITEMS ;
2022-08-18 23:03:28 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.MUSIC_FLAG_ALBUM ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.MUSIC_FLAG_ARTIST ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.MUSIC_FLAG_DURATION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.MUSIC_FLAG_STATE ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.MUSIC_FLAG_TRACK ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.MUSIC_FLAG_VOLUME ;
2016-07-25 00:00:22 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PROFILE ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_PROFILE ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefIntValue ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefStringValue ;
2021-05-27 23:11:00 +02:00
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_NAME ;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_WEIGHT_KG ;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_YEAR_OF_BIRTH ;
2019-11-21 13:24:06 +01:00
import static nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL ;
2019-12-29 11:07:42 +01:00
2022-08-18 23:03:28 +02:00
public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements Huami2021Handler {
2016-07-25 00:00:22 +02:00
2017-09-05 22:37:41 +02:00
// We introduce key press counter for notification purposes
2017-09-10 21:11:50 +02:00
private static int currentButtonActionId = 0 ;
2017-09-05 22:37:41 +02:00
private static int currentButtonPressCount = 0 ;
private static long currentButtonPressTime = 0 ;
2017-09-10 21:11:50 +02:00
private static long currentButtonTimerActivationTime = 0 ;
2021-09-01 00:16:50 +02:00
2020-01-08 19:58:31 +01:00
private Timer buttonActionTimer = null ;
2022-09-19 14:46:02 +02:00
private Timer findDeviceLoopTimer = null ;
2017-09-05 22:37:41 +02:00
2018-08-01 22:56:01 +02:00
private static final Logger LOG = LoggerFactory . getLogger ( HuamiSupport . class ) ;
private final DeviceInfoProfile < HuamiSupport > deviceInfoProfile ;
2018-08-18 00:39:14 +02:00
private final IntentListener mListener = new IntentListener ( ) {
2016-07-25 00:00:22 +02:00
@Override
2018-08-18 00:39:14 +02:00
public void notify ( Intent intent ) {
2016-07-25 00:00:22 +02:00
String s = intent . getAction ( ) ;
2018-08-06 19:54:33 +02:00
if ( DeviceInfoProfile . ACTION_DEVICE_INFO . equals ( s ) ) {
2016-07-25 00:00:22 +02:00
handleDeviceInfo ( ( nodomain . freeyourgadget . gadgetbridge . service . btle . profiles . deviceinfo . DeviceInfo ) intent . getParcelableExtra ( DeviceInfoProfile . EXTRA_DEVICE_INFO ) ) ;
}
}
} ;
2022-09-04 21:43:39 +02:00
protected BluetoothGattCharacteristic characteristicHRControlPoint ;
2019-12-26 23:05:13 +01:00
private BluetoothGattCharacteristic characteristicChunked ;
2017-10-23 10:28:54 +02:00
2021-08-16 12:36:14 +02:00
private BluetoothGattCharacteristic characteristicChunked2021Write ;
private BluetoothGattCharacteristic characteristicChunked2021Read ;
2016-09-20 23:09:42 +02:00
private boolean needsAuth ;
2016-07-25 00:00:22 +02:00
private volatile boolean telephoneRinging ;
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo ( ) ;
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo ( ) ;
2018-01-19 23:10:08 +01:00
private final GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone ( ) ;
2016-11-29 23:22:36 +01:00
private RealtimeSamplesSupport realtimeSamplesSupport ;
2016-07-25 00:00:22 +02:00
2020-05-18 08:21:31 +02:00
protected boolean isMusicAppStarted = false ;
protected MusicSpec bufferMusicSpec = null ;
protected MusicStateSpec bufferMusicStateSpec = null ;
2018-09-17 23:01:01 +02:00
private boolean heartRateNotifyEnabled ;
2019-12-26 23:05:13 +01:00
private int mMTU = 23 ;
2020-07-22 11:03:30 +02:00
protected int mActivitySampleSize = 4 ;
2022-08-18 23:03:28 +02:00
protected Huami2021ChunkedEncoder huami2021ChunkedEncoder ;
protected Huami2021ChunkedDecoder huami2021ChunkedDecoder ;
2018-08-06 23:11:40 +02:00
2023-05-21 00:34:36 +02:00
private final Queue < AbstractFetchOperation > fetchOperationQueue = new LinkedList < > ( ) ;
2018-08-01 22:56:01 +02:00
public HuamiSupport ( ) {
2017-09-27 21:46:23 +02:00
this ( LOG ) ;
}
2018-08-01 22:56:01 +02:00
public HuamiSupport ( Logger logger ) {
2017-09-27 21:46:23 +02:00
super ( logger ) ;
2016-07-25 00:00:22 +02:00
addSupportedService ( GattService . UUID_SERVICE_GENERIC_ACCESS ) ;
addSupportedService ( GattService . UUID_SERVICE_GENERIC_ATTRIBUTE ) ;
addSupportedService ( GattService . UUID_SERVICE_HEART_RATE ) ;
addSupportedService ( GattService . UUID_SERVICE_IMMEDIATE_ALERT ) ;
addSupportedService ( GattService . UUID_SERVICE_DEVICE_INFORMATION ) ;
addSupportedService ( GattService . UUID_SERVICE_ALERT_NOTIFICATION ) ;
addSupportedService ( MiBandService . UUID_SERVICE_MIBAND_SERVICE ) ;
addSupportedService ( MiBandService . UUID_SERVICE_MIBAND2_SERVICE ) ;
2018-08-02 10:55:30 +02:00
addSupportedService ( HuamiService . UUID_SERVICE_FIRMWARE_SERVICE ) ;
2016-07-25 00:00:22 +02:00
deviceInfoProfile = new DeviceInfoProfile < > ( this ) ;
2018-08-18 00:39:14 +02:00
deviceInfoProfile . addListener ( mListener ) ;
2016-07-25 00:00:22 +02:00
addSupportedProfile ( deviceInfoProfile ) ;
}
@Override
protected TransactionBuilder initializeDevice ( TransactionBuilder builder ) {
2016-09-20 23:09:42 +02:00
try {
2019-07-23 08:56:26 +02:00
byte authFlags = getAuthFlags ( ) ;
byte cryptFlags = getCryptFlags ( ) ;
2018-09-17 23:01:01 +02:00
heartRateNotifyEnabled = false ;
2019-07-23 08:56:26 +02:00
boolean authenticate = needsAuth & & ( cryptFlags = = 0x00 ) ;
2016-09-20 23:09:42 +02:00
needsAuth = false ;
2021-08-16 12:36:14 +02:00
characteristicChunked2021Read = getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ ) ;
2022-08-18 23:03:28 +02:00
if ( characteristicChunked2021Read ! = null & & huami2021ChunkedDecoder = = null ) {
huami2021ChunkedDecoder = new Huami2021ChunkedDecoder ( this , force2021Protocol ( ) ) ;
2022-02-02 12:57:25 +01:00
}
characteristicChunked2021Write = getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE ) ;
2022-08-18 23:03:28 +02:00
if ( characteristicChunked2021Write ! = null & & huami2021ChunkedEncoder = = null ) {
huami2021ChunkedEncoder = new Huami2021ChunkedEncoder ( characteristicChunked2021Write , force2021Protocol ( ) , mMTU ) ;
}
if ( characteristicChunked2021Write ! = null & & force2021Protocol ( ) ) {
new InitOperation2021 ( authenticate , authFlags , cryptFlags , this , builder , huami2021ChunkedEncoder , huami2021ChunkedDecoder ) . perform ( ) ;
2021-08-16 12:36:14 +02:00
} else {
new InitOperation ( authenticate , authFlags , cryptFlags , this , builder ) . perform ( ) ;
}
2017-10-23 10:28:54 +02:00
characteristicHRControlPoint = getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT ) ;
2018-08-02 10:55:30 +02:00
characteristicChunked = getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_CHUNKEDTRANSFER ) ;
2016-09-20 23:09:42 +02:00
} catch ( IOException e ) {
2019-07-23 08:56:26 +02:00
GB . toast ( getContext ( ) , " Initializing Huami device failed " , Toast . LENGTH_SHORT , GB . ERROR , e ) ;
2016-09-20 23:09:42 +02:00
}
2016-07-25 00:00:22 +02:00
return builder ;
}
2018-10-03 23:13:56 +02:00
protected byte getAuthFlags ( ) {
return HuamiService . AUTH_BYTE ;
}
2019-07-23 08:56:26 +02:00
public byte getCryptFlags ( ) {
return 0x00 ;
}
2020-09-30 18:47:19 +02:00
public boolean supportsSunriseSunsetWindHumidity ( ) {
2020-09-30 18:16:25 +02:00
return false ;
}
2021-04-21 17:12:20 +02:00
/ * *
* Return the wind speed as sting in a format that is supported by the device .
*
* A lot of devices only support " levels " , in GB we send the Beaufort speed .
* Override this in the device specific support class if other , more clear ,
* formats are supported .
*
* @param weatherSpec
* @return
* /
public String windSpeedString ( WeatherSpec weatherSpec ) {
return weatherSpec . windSpeedAsBeaufort ( ) + " " ; // cast to string
}
2022-12-15 22:35:54 +01:00
/ * *
* Returns the given date / time ( calendar ) as a byte sequence , suitable for sending to the
* Mi Band 2 ( or derivative ) . The band appears to not handle DST offsets , so we simply add this
* to the timezone .
*
* @param calendar
* @param precision
* @return
* /
2022-05-28 23:56:56 +02:00
public byte [ ] getTimeBytes ( Calendar calendar , TimeUnit precision ) {
2022-12-15 22:35:54 +01:00
byte [ ] bytes ;
2022-05-28 23:56:56 +02:00
if ( precision = = TimeUnit . MINUTES ) {
2022-12-15 22:35:54 +01:00
bytes = BLETypeConversions . shortCalendarToRawBytes ( calendar ) ;
2022-05-28 23:56:56 +02:00
} else if ( precision = = TimeUnit . SECONDS ) {
2022-12-15 22:35:54 +01:00
bytes = calendarToRawBytes ( calendar ) ;
2022-05-28 23:56:56 +02:00
} else {
throw new IllegalArgumentException ( " Unsupported precision, only MINUTES and SECONDS are supported till now " ) ;
2016-12-01 22:49:58 +01:00
}
2022-12-15 22:35:54 +01:00
byte [ ] tail = new byte [ ] { 0 , BLETypeConversions . mapTimeZone ( calendar , BLETypeConversions . TZ_FLAG_INCLUDE_DST_IN_TZ ) } ;
// 0 = adjust reason bitflags? or DST offset?? , timezone
// byte[] tail = new byte[] { 0x2 }; // reason
byte [ ] all = BLETypeConversions . join ( bytes , tail ) ;
return all ;
}
/ * *
* Converts a timestamp to the byte sequence to be sent to the current time characteristic
*
* @param timestamp
* @return
* @see GattCharacteristic # UUID_CHARACTERISTIC_CURRENT_TIME
* /
public static byte [ ] calendarToRawBytes ( Calendar timestamp ) {
// MiBand2:
// year,year,month,dayofmonth,hour,minute,second,dayofweek,0,0,tz
byte [ ] year = BLETypeConversions . fromUint16 ( timestamp . get ( Calendar . YEAR ) ) ;
return new byte [ ] {
year [ 0 ] ,
year [ 1 ] ,
BLETypeConversions . fromUint8 ( timestamp . get ( Calendar . MONTH ) + 1 ) ,
BLETypeConversions . fromUint8 ( timestamp . get ( Calendar . DATE ) ) ,
BLETypeConversions . fromUint8 ( timestamp . get ( Calendar . HOUR_OF_DAY ) ) ,
BLETypeConversions . fromUint8 ( timestamp . get ( Calendar . MINUTE ) ) ,
BLETypeConversions . fromUint8 ( timestamp . get ( Calendar . SECOND ) ) ,
BLETypeConversions . dayOfWeekToRawBytes ( timestamp ) ,
0 , // fractions256 (not set)
// 0 (DST offset?) Mi2
// k (tz) Mi2
} ;
2016-11-18 21:14:04 +01:00
}
public Calendar fromTimeBytes ( byte [ ] bytes ) {
2019-06-05 14:11:44 +02:00
GregorianCalendar timestamp = BLETypeConversions . rawBytesToCalendar ( bytes ) ;
2016-11-18 21:14:04 +01:00
return timestamp ;
}
2018-08-01 22:56:01 +02:00
public HuamiSupport setCurrentTimeWithService ( TransactionBuilder builder ) {
2022-12-15 22:35:54 +01:00
final Calendar now = createCalendar ( ) ;
byte [ ] bytes = getTimeBytes ( now , TimeUnit . SECONDS ) ;
2016-11-18 21:14:04 +01:00
builder . write ( getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_CURRENT_TIME ) , bytes ) ;
2016-08-17 00:53:16 +02:00
return this ;
}
2022-12-15 22:35:54 +01:00
/ * *
* Allow for the calendar to be overridden to a fixed date , for tests .
* /
protected Calendar createCalendar ( ) {
return BLETypeConversions . createCalendar ( ) ;
}
2016-07-25 00:00:22 +02:00
/ * *
* Last action of initialization sequence . Sets the device to initialized .
* It is only invoked if all other actions were successfully run , so the device
* must be initialized , then .
*
* @param builder
* /
2016-09-20 23:09:42 +02:00
public void setInitialized ( TransactionBuilder builder ) {
2019-05-20 16:36:06 +02:00
builder . add ( new SetDeviceStateAction ( gbDevice , State . INITIALIZED , getContext ( ) ) ) ;
2016-07-25 00:00:22 +02:00
}
// MB2: AVL
// TODO: tear down the notifications on quit
2018-08-01 22:56:01 +02:00
public HuamiSupport enableNotifications ( TransactionBuilder builder , boolean enable ) {
2016-07-25 00:00:22 +02:00
builder . notify ( getCharacteristic ( MiBandService . UUID_CHARACTERISTIC_NOTIFICATION ) , enable ) ;
2016-08-17 00:53:16 +02:00
builder . notify ( getCharacteristic ( GattService . UUID_SERVICE_CURRENT_TIME ) , enable ) ;
2016-09-12 19:28:50 +02:00
// Notify CHARACTERISTIC9 to receive random auth code
2018-08-02 10:55:30 +02:00
builder . notify ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_AUTH ) , enable ) ;
2021-08-16 12:36:14 +02:00
if ( characteristicChunked2021Read ! = null ) {
builder . notify ( characteristicChunked2021Read , enable ) ;
}
2019-12-26 23:26:08 +01:00
2016-07-25 00:00:22 +02:00
return this ;
}
2018-08-01 22:56:01 +02:00
public HuamiSupport enableFurtherNotifications ( TransactionBuilder builder , boolean enable ) {
2018-08-02 10:55:30 +02:00
builder . notify ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , enable ) ;
builder . notify ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_6_BATTERY_INFO ) , enable ) ;
2019-08-02 00:11:11 +02:00
builder . notify ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_AUDIO ) , enable ) ;
builder . notify ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_AUDIODATA ) , enable ) ;
2019-12-26 23:26:08 +01:00
builder . notify ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_DEVICEEVENT ) , enable ) ;
2022-06-04 22:20:28 +02:00
builder . notify ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_WORKOUT ) , enable ) ;
2022-02-02 12:57:25 +01:00
if ( characteristicChunked2021Read ! = null ) {
builder . notify ( characteristicChunked2021Read , enable ) ;
}
2016-07-25 00:00:22 +02:00
return this ;
}
@Override
public boolean useAutoConnect ( ) {
return true ;
}
@Override
2017-04-12 21:33:19 +02:00
public boolean connectFirstTime ( ) {
2016-09-20 23:09:42 +02:00
needsAuth = true ;
2020-08-28 15:38:18 +02:00
return connect ( ) ;
2016-07-25 00:00:22 +02:00
}
2018-08-01 22:56:01 +02:00
private HuamiSupport sendDefaultNotification ( TransactionBuilder builder , SimpleNotification simpleNotification , short repeat , BtLEAction extraAction ) {
2016-07-25 00:00:22 +02:00
LOG . info ( " Sending notification to MiBand: ( " + repeat + " times) " ) ;
NotificationStrategy strategy = getNotificationStrategy ( ) ;
for ( short i = 0 ; i < repeat ; i + + ) {
2017-03-02 00:27:54 +01:00
strategy . sendDefaultNotification ( builder , simpleNotification , extraAction ) ;
2016-07-25 00:00:22 +02:00
}
return this ;
}
2017-08-13 16:31:11 +02:00
public NotificationStrategy getNotificationStrategy ( ) {
2019-05-20 16:36:06 +02:00
String firmwareVersion = gbDevice . getFirmwareVersion ( ) ;
2017-03-15 00:26:39 +01:00
if ( firmwareVersion ! = null ) {
Version ver = new Version ( firmwareVersion ) ;
if ( MiBandConst . MI2_FW_VERSION_MIN_TEXT_NOTIFICATIONS . compareTo ( ver ) > 0 ) {
return new Mi2NotificationStrategy ( this ) ;
}
}
2019-05-23 21:33:35 +02:00
if ( GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) . getBoolean ( MiBandConst . PREF_MI2_ENABLE_TEXT_NOTIFICATIONS , true ) ) {
2017-03-15 00:26:39 +01:00
return new Mi2TextNotificationStrategy ( this ) ;
}
return new Mi2NotificationStrategy ( this ) ;
2016-07-25 00:00:22 +02:00
}
2017-03-05 21:45:39 +01:00
private static final byte [ ] startHeartMeasurementManual = new byte [ ] { 0x15 , MiBandService . COMMAND_SET_HR_MANUAL , 1 } ;
private static final byte [ ] stopHeartMeasurementManual = new byte [ ] { 0x15 , MiBandService . COMMAND_SET_HR_MANUAL , 0 } ;
private static final byte [ ] startHeartMeasurementContinuous = new byte [ ] { 0x15 , MiBandService . COMMAND_SET__HR_CONTINUOUS , 1 } ;
private static final byte [ ] stopHeartMeasurementContinuous = new byte [ ] { 0x15 , MiBandService . COMMAND_SET__HR_CONTINUOUS , 0 } ;
2016-07-25 00:00:22 +02:00
2022-08-18 23:03:28 +02:00
protected HuamiSupport requestBatteryInfo ( TransactionBuilder builder ) {
2016-12-14 00:50:43 +01:00
LOG . debug ( " Requesting Battery Info! " ) ;
2018-08-02 10:55:30 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_6_BATTERY_INFO ) ;
2016-12-14 00:50:43 +01:00
builder . read ( characteristic ) ;
return this ;
}
2018-08-01 22:56:01 +02:00
public HuamiSupport requestDeviceInfo ( TransactionBuilder builder ) {
2016-07-25 00:00:22 +02:00
LOG . debug ( " Requesting Device Info! " ) ;
deviceInfoProfile . requestDeviceInfo ( builder ) ;
return this ;
}
/ * *
* Part of device initialization process . Do not call manually .
*
* @param transaction
* @return
* /
2022-07-30 21:37:21 +02:00
protected HuamiSupport setFitnessGoal ( TransactionBuilder transaction ) {
2016-07-25 00:00:22 +02:00
LOG . info ( " Attempting to set Fitness Goal... " ) ;
2018-08-02 10:55:30 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_8_USER_SETTINGS ) ;
2016-07-25 00:00:22 +02:00
if ( characteristic ! = null ) {
2018-09-16 20:49:00 +02:00
int fitnessGoal = GBApplication . getPrefs ( ) . getInt ( ActivityUser . PREF_USER_STEPS_GOAL , ActivityUser . defaultUserStepsGoal ) ;
2016-11-24 21:58:32 +01:00
byte [ ] bytes = ArrayUtils . addAll (
2018-08-02 10:55:30 +02:00
HuamiService . COMMAND_SET_FITNESS_GOAL_START ,
2016-11-24 21:58:32 +01:00
BLETypeConversions . fromUint16 ( fitnessGoal ) ) ;
bytes = ArrayUtils . addAll ( bytes ,
2018-08-02 10:55:30 +02:00
HuamiService . COMMAND_SET_FITNESS_GOAL_END ) ;
2016-11-24 21:58:32 +01:00
transaction . write ( characteristic , bytes ) ;
2016-07-25 00:00:22 +02:00
} else {
LOG . info ( " Unable to set Fitness Goal " ) ;
}
return this ;
}
2017-09-23 00:08:34 +02:00
/ * *
* Part of device initialization process . Do not call manually .
*
* @param transaction
* @return
* /
2022-08-18 23:03:28 +02:00
protected HuamiSupport setUserInfo ( TransactionBuilder transaction ) {
2018-08-02 10:55:30 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_8_USER_SETTINGS ) ;
2017-09-24 23:03:11 +02:00
if ( characteristic = = null ) {
return this ;
}
LOG . info ( " Attempting to set user info... " ) ;
2017-09-23 00:08:34 +02:00
Prefs prefs = GBApplication . getPrefs ( ) ;
2021-05-27 23:11:00 +02:00
String alias = prefs . getString ( PREF_USER_NAME , null ) ;
2017-09-24 23:03:11 +02:00
ActivityUser activityUser = new ActivityUser ( ) ;
int height = activityUser . getHeightCm ( ) ;
int weight = activityUser . getWeightKg ( ) ;
int birth_year = activityUser . getYearOfBirth ( ) ;
byte birth_month = 7 ; // not in user attributes
byte birth_day = 1 ; // not in user attributes
if ( alias = = null | | weight = = 0 | | height = = 0 | | birth_year = = 0 ) {
LOG . warn ( " Unable to set user info, make sure it is set up " ) ;
return this ;
}
2017-09-23 00:08:34 +02:00
2017-09-24 23:03:11 +02:00
byte sex = 2 ; // other
switch ( activityUser . getGender ( ) ) {
case ActivityUser . GENDER_MALE :
sex = 0 ;
break ;
case ActivityUser . GENDER_FEMALE :
sex = 1 ;
2017-09-23 00:08:34 +02:00
}
2017-09-24 23:03:11 +02:00
int userid = alias . hashCode ( ) ; // hash from alias like mi1
// FIXME: Do encoding like in PebbleProtocol, this is ugly
2019-09-14 00:05:39 +02:00
byte [ ] bytes = new byte [ ] {
2018-08-02 10:55:30 +02:00
HuamiService . COMMAND_SET_USERINFO ,
2017-09-24 23:03:11 +02:00
0 ,
0 ,
( byte ) ( birth_year & 0xff ) ,
( byte ) ( ( birth_year > > 8 ) & 0xff ) ,
birth_month ,
birth_day ,
sex ,
( byte ) ( height & 0xff ) ,
( byte ) ( ( height > > 8 ) & 0xff ) ,
( byte ) ( ( weight * 200 ) & 0xff ) ,
( byte ) ( ( ( weight * 200 ) > > 8 ) & 0xff ) ,
( byte ) ( userid & 0xff ) ,
( byte ) ( ( userid > > 8 ) & 0xff ) ,
( byte ) ( ( userid > > 16 ) & 0xff ) ,
( byte ) ( ( userid > > 24 ) & 0xff )
} ;
transaction . write ( characteristic , bytes ) ;
2017-09-23 00:08:34 +02:00
return this ;
}
2016-07-25 00:00:22 +02:00
/ * *
* Part of device initialization process . Do not call manually .
*
2016-11-13 01:42:55 +01:00
* @param builder
2016-07-25 00:00:22 +02:00
* @return
* /
2022-08-18 23:03:28 +02:00
protected HuamiSupport setWearLocation ( TransactionBuilder builder ) {
2016-07-25 00:00:22 +02:00
LOG . info ( " Attempting to set wear location... " ) ;
2018-08-02 10:55:30 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_8_USER_SETTINGS ) ;
2016-07-25 00:00:22 +02:00
if ( characteristic ! = null ) {
2016-11-13 20:47:24 +01:00
builder . notify ( characteristic , true ) ;
2019-05-20 16:36:06 +02:00
int location = MiBandCoordinator . getWearLocation ( gbDevice . getAddress ( ) ) ;
2016-11-13 01:42:55 +01:00
switch ( location ) {
case 0 : // left hand
2018-08-02 10:55:30 +02:00
builder . write ( characteristic , HuamiService . WEAR_LOCATION_LEFT_WRIST ) ;
2016-11-13 01:42:55 +01:00
break ;
2016-11-24 21:58:32 +01:00
case 1 : // right hand
2018-08-02 10:55:30 +02:00
builder . write ( characteristic , HuamiService . WEAR_LOCATION_RIGHT_WRIST ) ;
2016-11-13 01:42:55 +01:00
break ;
}
2016-11-18 21:14:04 +01:00
builder . notify ( characteristic , false ) ; // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
2016-07-25 00:00:22 +02:00
}
return this ;
}
@Override
public void onEnableHeartRateSleepSupport ( boolean enable ) {
try {
TransactionBuilder builder = performInitialized ( " enable heart rate sleep support: " + enable ) ;
setHeartrateSleepSupport ( builder ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
GB . toast ( getContext ( ) , " Error toggling heart rate sleep support: " + e . getLocalizedMessage ( ) , Toast . LENGTH_LONG , GB . ERROR ) ;
}
}
2017-11-11 00:04:51 +01:00
@Override
public void onSetHeartRateMeasurementInterval ( int seconds ) {
try {
int minuteInterval = seconds / 60 ;
minuteInterval = Math . min ( minuteInterval , 120 ) ;
minuteInterval = Math . max ( 0 , minuteInterval ) ;
TransactionBuilder builder = performInitialized ( " set heart rate interval to: " + minuteInterval + " minutes " ) ;
setHeartrateMeasurementInterval ( builder , minuteInterval ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
GB . toast ( getContext ( ) , " Error toggling heart rate sleep support: " + e . getLocalizedMessage ( ) , Toast . LENGTH_LONG , GB . ERROR ) ;
}
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setPassword ( final TransactionBuilder builder ) {
2022-07-05 20:29:16 +02:00
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 ;
}
2016-07-25 00:00:22 +02:00
/ * *
* Part of device initialization process . Do not call manually .
*
* @param builder
* /
2022-08-15 14:07:03 +02:00
protected HuamiSupport setHeartrateSleepSupport ( TransactionBuilder builder ) {
2019-05-20 16:36:06 +02:00
final boolean enableHrSleepSupport = MiBandCoordinator . getHeartrateSleepSupport ( gbDevice . getAddress ( ) ) ;
2016-11-13 01:42:55 +01:00
if ( characteristicHRControlPoint ! = null ) {
2016-11-13 20:47:24 +01:00
builder . notify ( characteristicHRControlPoint , true ) ;
2016-11-13 01:42:55 +01:00
if ( enableHrSleepSupport ) {
LOG . info ( " Enabling heartrate sleep support... " ) ;
2018-08-02 10:55:30 +02:00
builder . write ( characteristicHRControlPoint , HuamiService . COMMAND_ENABLE_HR_SLEEP_MEASUREMENT ) ;
2016-11-13 01:42:55 +01:00
} else {
LOG . info ( " Disabling heartrate sleep support... " ) ;
2018-08-02 10:55:30 +02:00
builder . write ( characteristicHRControlPoint , HuamiService . COMMAND_DISABLE_HR_SLEEP_MEASUREMENT ) ;
2016-11-13 01:42:55 +01:00
}
2016-11-18 21:14:04 +01:00
builder . notify ( characteristicHRControlPoint , false ) ; // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
2016-07-25 00:00:22 +02:00
}
return this ;
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setHeartrateActivityMonitoring ( TransactionBuilder builder ) {
2022-05-16 18:42:02 +02:00
final boolean enableHrActivityMonitoring = HuamiCoordinator . getHeartrateActivityMonitoring ( gbDevice . getAddress ( ) ) ;
final byte [ ] cmd = { ENDPOINT_DISPLAY , 0x22 , 0x00 , ( byte ) ( enableHrActivityMonitoring ? 0x01 : 0x00 ) } ;
writeToConfiguration ( builder , cmd ) ;
return this ;
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setHeartrateAlert ( TransactionBuilder builder ) {
2022-05-16 18:42:02 +02:00
final boolean enableHrAlert = HuamiCoordinator . getHeartrateAlert ( gbDevice . getAddress ( ) ) ;
2022-08-18 23:03:28 +02:00
final int hrAlertThreshold = HuamiCoordinator . getHeartrateAlertHighThreshold ( gbDevice . getAddress ( ) ) ;
2022-05-16 18:42:02 +02:00
final byte [ ] cmd = {
ENDPOINT_DISPLAY ,
0x1a ,
0x00 ,
( byte ) ( enableHrAlert ? 0x01 : 0x00 ) ,
( byte ) hrAlertThreshold
} ;
writeToConfiguration ( builder , cmd ) ;
return this ;
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setHeartrateStressMonitoring ( TransactionBuilder builder ) {
2022-05-16 18:42:02 +02:00
final boolean enableHrStressMonitoring = HuamiCoordinator . getHeartrateStressMonitoring ( gbDevice . getAddress ( ) ) ;
2022-08-18 23:03:28 +02:00
LOG . info ( " Setting heart rate stress monitoring to {} " , enableHrStressMonitoring ) ;
2022-05-16 18:42:02 +02:00
final byte [ ] cmd = new byte [ ] { ( byte ) 0xfe , 0x06 , 0x00 , ( byte ) ( enableHrStressMonitoring ? 0x01 : 0x00 ) } ;
writeToConfiguration ( builder , cmd ) ;
return this ;
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setHeartrateMeasurementInterval ( TransactionBuilder builder , int minutes ) {
2017-11-11 00:04:51 +01:00
if ( characteristicHRControlPoint ! = null ) {
builder . notify ( characteristicHRControlPoint , true ) ;
LOG . info ( " Setting heart rate measurement interval to " + minutes + " minutes " ) ;
2018-08-02 10:55:30 +02:00
builder . write ( characteristicHRControlPoint , new byte [ ] { HuamiService . COMMAND_SET_PERIODIC_HR_MEASUREMENT_INTERVAL , ( byte ) minutes } ) ;
2017-11-11 00:04:51 +01:00
builder . notify ( characteristicHRControlPoint , false ) ; // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
}
return this ;
}
2017-03-02 00:27:54 +01:00
private void performDefaultNotification ( String task , SimpleNotification simpleNotification , short repeat , BtLEAction extraAction ) {
2016-07-25 00:00:22 +02:00
try {
TransactionBuilder builder = performInitialized ( task ) ;
2017-03-02 00:27:54 +01:00
sendDefaultNotification ( builder , simpleNotification , repeat , extraAction ) ;
2016-07-25 00:00:22 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to send notification to MI device " , ex ) ;
}
}
2022-06-14 20:15:51 +02:00
protected void performPreferredNotification ( String task , String notificationOrigin , SimpleNotification simpleNotification , int alertLevel , BtLEAction extraAction ) {
2016-07-25 00:00:22 +02:00
try {
TransactionBuilder builder = performInitialized ( task ) ;
Prefs prefs = GBApplication . getPrefs ( ) ;
short vibrateTimes = getPreferredVibrateCount ( notificationOrigin , prefs ) ;
VibrationProfile profile = getPreferredVibrateProfile ( notificationOrigin , prefs , vibrateTimes ) ;
2016-10-02 23:04:59 +02:00
profile . setAlertLevel ( alertLevel ) ;
2016-07-25 00:00:22 +02:00
2019-08-29 08:32:29 +02:00
getNotificationStrategy ( ) . sendCustomNotification ( profile , simpleNotification , 0 , 0 , 0 , 0 , extraAction , builder ) ;
2017-03-14 00:44:59 +01:00
2016-07-25 00:00:22 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
2019-08-29 08:32:29 +02:00
LOG . error ( " Unable to send notification to device " , ex ) ;
2016-07-25 00:00:22 +02:00
}
}
private short getPreferredVibrateCount ( String notificationOrigin , Prefs prefs ) {
return ( short ) Math . min ( Short . MAX_VALUE , getNotificationPrefIntValue ( VIBRATION_COUNT , notificationOrigin , prefs , DEFAULT_VALUE_VIBRATION_COUNT ) ) ;
}
private VibrationProfile getPreferredVibrateProfile ( String notificationOrigin , Prefs prefs , short repeat ) {
String profileId = getNotificationPrefStringValue ( VIBRATION_PROFILE , notificationOrigin , prefs , DEFAULT_VALUE_VIBRATION_PROFILE ) ;
return VibrationProfile . getProfile ( profileId , repeat ) ;
}
@Override
public void onSetAlarms ( ArrayList < ? extends Alarm > alarms ) {
2022-08-18 23:03:28 +02:00
final DeviceCoordinator coordinator = DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
2023-06-13 23:02:26 +02:00
int maxAlarms = coordinator . getAlarmSlotCount ( gbDevice ) ;
2022-08-18 23:03:28 +02:00
2016-07-25 00:00:22 +02:00
try {
TransactionBuilder builder = performInitialized ( " Set alarm " ) ;
boolean anyAlarmEnabled = false ;
for ( Alarm alarm : alarms ) {
2022-08-18 23:03:28 +02:00
if ( alarm . getPosition ( ) > = maxAlarms ) {
if ( alarm . getEnabled ( ) ) {
GB . toast ( getContext ( ) , " Only " + maxAlarms + " alarms are currently supported. " , Toast . LENGTH_LONG , GB . WARN ) ;
}
break ;
}
2019-01-07 01:10:57 +01:00
anyAlarmEnabled | = alarm . getEnabled ( ) ;
2021-09-01 13:38:48 +02:00
queueAlarm ( alarm , builder ) ;
2016-07-25 00:00:22 +02:00
}
builder . queue ( getQueue ( ) ) ;
if ( anyAlarmEnabled ) {
GB . toast ( getContext ( ) , getContext ( ) . getString ( R . string . user_feedback_miband_set_alarms_ok ) , Toast . LENGTH_SHORT , GB . INFO ) ;
} else {
GB . toast ( getContext ( ) , getContext ( ) . getString ( R . string . user_feedback_all_alarms_disabled ) , Toast . LENGTH_SHORT , GB . INFO ) ;
}
} catch ( IOException ex ) {
GB . toast ( getContext ( ) , getContext ( ) . getString ( R . string . user_feedback_miband_set_alarms_failed ) , Toast . LENGTH_LONG , GB . ERROR , ex ) ;
2019-10-31 14:28:24 +01:00
}
}
2022-11-26 18:34:09 +01:00
/ * *
* Contains the logic to build the text content that will be sent to the device .
* Some huami devices will omit some of the content .
* @param notificationSpec
* @return
* /
public String getNotificationBody ( NotificationSpec notificationSpec ) {
2019-10-31 14:28:24 +01:00
String senderOrTitle = StringUtils . getFirstOf ( notificationSpec . sender , notificationSpec . title ) ;
String message = StringUtils . truncate ( senderOrTitle , 32 ) + " \ 0 " ;
if ( notificationSpec . subject ! = null ) {
message + = StringUtils . truncate ( notificationSpec . subject , 128 ) + " \ n \ n " ;
}
if ( notificationSpec . body ! = null ) {
2020-06-15 10:45:39 +02:00
message + = StringUtils . truncate ( notificationSpec . body , 512 ) ;
2019-10-31 14:28:24 +01:00
}
2020-12-02 17:07:40 +01:00
if ( notificationSpec . body = = null & & notificationSpec . subject = = null ) {
message + = " " ; // if we have no body we have to send at least something on some devices, else they reboot (Bip S)
}
2019-10-31 14:28:24 +01:00
2022-11-26 18:34:09 +01:00
return message ;
}
@Override
public void onNotification ( NotificationSpec notificationSpec ) {
final boolean hasExtraHeader = notificationHasExtraHeader ( ) ;
final int maxLength = notificationMaxLength ( ) ;
String message = getNotificationBody ( notificationSpec ) ;
2019-10-31 14:28:24 +01:00
try {
TransactionBuilder builder = performInitialized ( " new notification " ) ;
byte customIconId = HuamiIcon . mapToIconId ( notificationSpec . type ) ;
AlertCategory alertCategory = AlertCategory . CustomHuami ;
// The SMS icon for AlertCategory.SMS is unique and not available as iconId
if ( notificationSpec . type = = NotificationType . GENERIC_SMS ) {
alertCategory = AlertCategory . SMS ;
}
// EMAIL icon does not work in FW 0.0.8.74, it did in 0.0.7.90
else if ( customIconId = = HuamiIcon . EMAIL ) {
alertCategory = AlertCategory . Email ;
}
if ( characteristicChunked ! = null ) {
int prefixlength = 2 ;
// We also need a (fake) source name for Mi Band 3 for SMS/EMAIL, else the message is not displayed
byte [ ] appSuffix = " \ 0 \ 0 " . getBytes ( ) ;
int suffixlength = appSuffix . length ;
if ( alertCategory = = AlertCategory . CustomHuami ) {
String appName ;
prefixlength = 3 ;
final PackageManager pm = getContext ( ) . getPackageManager ( ) ;
ApplicationInfo ai = null ;
try {
ai = pm . getApplicationInfo ( notificationSpec . sourceAppId , 0 ) ;
} catch ( PackageManager . NameNotFoundException ignored ) {
}
if ( ai ! = null ) {
appName = " \ 0 " + pm . getApplicationLabel ( ai ) + " \ 0 " ;
} else {
appName = " \ 0 " + " UNKNOWN " + " \ 0 " ;
}
appSuffix = appName . getBytes ( ) ;
suffixlength = appSuffix . length ;
}
if ( hasExtraHeader ) {
prefixlength + = 4 ;
}
2022-06-14 20:15:51 +02:00
// final step: build command
2019-10-31 14:28:24 +01:00
byte [ ] rawmessage = message . getBytes ( ) ;
int length = Math . min ( rawmessage . length , maxLength - prefixlength ) ;
2020-06-15 11:44:58 +02:00
if ( length < rawmessage . length ) {
length = StringUtils . utf8ByteLength ( message , length ) ;
}
2019-10-31 14:28:24 +01:00
byte [ ] command = new byte [ length + prefixlength + suffixlength ] ;
int pos = 0 ;
command [ pos + + ] = ( byte ) alertCategory . getId ( ) ;
if ( hasExtraHeader ) {
command [ pos + + ] = 0 ; // TODO
command [ pos + + ] = 0 ;
command [ pos + + ] = 0 ;
command [ pos + + ] = 0 ;
}
command [ pos + + ] = 1 ;
if ( alertCategory = = AlertCategory . CustomHuami ) {
command [ pos ] = customIconId ;
}
System . arraycopy ( rawmessage , 0 , command , prefixlength , length ) ;
System . arraycopy ( appSuffix , 0 , command , prefixlength + length , appSuffix . length ) ;
writeToChunked ( builder , 0 , command ) ;
} else {
AlertNotificationProfile < ? > profile = new AlertNotificationProfile ( this ) ;
NewAlert alert = new NewAlert ( alertCategory , 1 , message , customIconId ) ;
profile . setMaxLength ( maxLength ) ;
profile . newAlert ( builder , alert ) ;
}
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to send notification to device " , ex ) ;
2016-07-25 00:00:22 +02:00
}
}
2022-06-14 20:15:51 +02:00
protected boolean notificationHasExtraHeader ( ) {
return false ;
}
protected int notificationMaxLength ( ) {
return 230 ;
}
2021-12-04 16:55:09 +01:00
@Override
public void onSetReminders ( ArrayList < ? extends Reminder > reminders ) {
final TransactionBuilder builder ;
try {
builder = performInitialized ( " onSetReminders " ) ;
} catch ( final IOException e ) {
LOG . error ( " Unable to send reminders to device " , e ) ;
return ;
}
sendReminders ( builder , reminders ) ;
builder . queue ( getQueue ( ) ) ;
}
private void sendReminders ( final TransactionBuilder builder ) {
final List < ? extends Reminder > reminders = DBHelper . getReminders ( gbDevice ) ;
sendReminders ( builder , reminders ) ;
}
2023-06-16 21:43:05 +02:00
private void sendReminders ( final TransactionBuilder builder , final List < ? extends Reminder > reminders ) {
2021-12-04 16:55:09 +01:00
final DeviceCoordinator coordinator = DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
final Prefs prefs = new Prefs ( GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ) ;
2022-08-18 23:03:28 +02:00
int reservedSlots = prefs . getInt ( PREF_RESERVER_REMINDERS_CALENDAR , coordinator . supportsCalendarEvents ( ) ? 0 : 9 ) ;
2021-12-04 16:55:09 +01:00
LOG . info ( " On Set Reminders. Reminders: {}, Reserved slots: {} " , reminders . size ( ) , reservedSlots ) ;
// Send the reminders, skipping the reserved slots for calendar events
for ( int i = 0 ; i < reminders . size ( ) ; i + + ) {
LOG . debug ( " Sending reminder at position {} " , i + reservedSlots ) ;
sendReminderToDevice ( builder , i + reservedSlots , reminders . get ( i ) ) ;
}
// Delete the remaining slots, skipping the sent reminders and reserved slots
2022-10-22 21:53:45 +02:00
final int reminderSlotCount = coordinator . getReminderSlotCount ( getDevice ( ) ) ;
for ( int i = reminders . size ( ) + reservedSlots ; i < reminderSlotCount ; i + + ) {
2021-12-04 16:55:09 +01:00
LOG . debug ( " Deleting reminder at position {} " , i ) ;
sendReminderToDevice ( builder , i , null ) ;
}
}
2023-06-16 21:43:05 +02:00
private void sendReminderToDevice ( final TransactionBuilder builder , int position , final Reminder reminder ) {
2021-12-04 16:55:09 +01:00
if ( characteristicChunked = = null ) {
LOG . warn ( " characteristicChunked is null, not sending reminder " ) ;
return ;
}
final DeviceCoordinator coordinator = DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
2022-10-22 21:53:45 +02:00
final int reminderSlotCount = coordinator . getReminderSlotCount ( getDevice ( ) ) ;
2021-12-04 16:55:09 +01:00
2022-10-22 21:53:45 +02:00
if ( position + 1 > reminderSlotCount ) {
LOG . error ( " Reminder for position {} is over the limit of {} reminders " , position , reminderSlotCount ) ;
2021-12-04 16:55:09 +01:00
return ;
}
if ( reminder = = null ) {
// Delete reminder
2021-12-12 11:48:22 +01:00
writeToChunked ( builder , 2 , new byte [ ] { ( byte ) 0x0b , ( byte ) ( position & 0xFF ) , 0x08 , 0 , 0 , 0 , 0 } ) ;
2021-12-04 16:55:09 +01:00
return ;
}
final ByteBuffer buf = ByteBuffer . allocate ( 14 + reminder . getMessage ( ) . getBytes ( ) . length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 0x0B ) ;
buf . put ( ( byte ) ( position & 0xFF ) ) ;
2021-12-12 23:58:52 +01:00
final Calendar cal = Calendar . getInstance ( ) ;
cal . setTime ( reminder . getDate ( ) ) ;
int eventConfig = 0x01 | 0x08 ; // flags 0x01 = enable, 0x04 = end date present (not on reminders), 0x08 = has text
2021-12-04 16:55:09 +01:00
switch ( reminder . getRepetition ( ) ) {
case Reminder . ONCE :
2021-12-12 23:58:52 +01:00
// Default is once, nothing to do
2021-12-04 16:55:09 +01:00
break ;
case Reminder . EVERY_DAY :
2021-12-12 23:58:52 +01:00
eventConfig | = 0x0fe0 ; // all week day bits set
2021-12-04 16:55:09 +01:00
break ;
case Reminder . EVERY_WEEK :
2021-12-12 23:58:52 +01:00
int dayOfWeek = BLETypeConversions . dayOfWeekToRawBytes ( cal ) - 1 ; // Monday = 0
eventConfig | = 0x20 < < dayOfWeek ;
2021-12-04 16:55:09 +01:00
break ;
case Reminder . EVERY_MONTH :
2021-12-12 23:58:52 +01:00
eventConfig | = 0x1000 ;
2021-12-04 16:55:09 +01:00
break ;
case Reminder . EVERY_YEAR :
2021-12-12 23:58:52 +01:00
eventConfig | = 0x2000 ;
2021-12-04 16:55:09 +01:00
break ;
default :
LOG . warn ( " Unknown repetition for reminder in position {}, defaulting to once " , position ) ;
}
2021-12-12 23:58:52 +01:00
buf . putInt ( eventConfig ) ;
2021-12-04 16:55:09 +01:00
buf . put ( BLETypeConversions . shortCalendarToRawBytes ( cal ) ) ;
buf . put ( ( byte ) 0x00 ) ;
if ( reminder . getMessage ( ) . getBytes ( ) . length > coordinator . getMaximumReminderMessageLength ( ) ) {
LOG . warn ( " The reminder message length {} is longer than {}, will be truncated " ,
reminder . getMessage ( ) . getBytes ( ) . length ,
coordinator . getMaximumReminderMessageLength ( )
) ;
buf . put ( Arrays . copyOf ( reminder . getMessage ( ) . getBytes ( ) , coordinator . getMaximumReminderMessageLength ( ) ) ) ;
} else {
buf . put ( reminder . getMessage ( ) . getBytes ( ) ) ;
}
buf . put ( ( byte ) 0x00 ) ;
writeToChunked ( builder , 2 , buf . array ( ) ) ;
}
2022-05-09 20:47:08 +02:00
@Override
public void onSetWorldClocks ( ArrayList < ? extends WorldClock > clocks ) {
final TransactionBuilder builder ;
try {
builder = performInitialized ( " onSetWorldClocks " ) ;
} catch ( final IOException e ) {
LOG . error ( " Unable to send world clocks to device " , e ) ;
return ;
}
sendWorldClocks ( builder , clocks ) ;
builder . queue ( getQueue ( ) ) ;
}
private void setWorldClocks ( final TransactionBuilder builder ) {
final List < ? extends WorldClock > clocks = DBHelper . getWorldClocks ( gbDevice ) ;
sendWorldClocks ( builder , clocks ) ;
}
2022-08-18 23:03:28 +02:00
protected void sendWorldClocks ( final TransactionBuilder builder , final List < ? extends WorldClock > clocks ) {
2022-05-09 20:47:08 +02:00
final DeviceCoordinator coordinator = DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
if ( coordinator . getWorldClocksSlotCount ( ) = = 0 ) {
return ;
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream ( ) ;
try {
baos . write ( 0x03 ) ;
2022-10-22 21:53:45 +02:00
2022-05-09 20:47:08 +02:00
if ( clocks . size ( ) ! = 0 ) {
2022-08-21 19:31:11 +02:00
baos . write ( clocks . size ( ) ) ;
int i = 0 ;
2022-05-09 20:47:08 +02:00
for ( final WorldClock clock : clocks ) {
2022-08-21 19:31:11 +02:00
baos . write ( i + + ) ;
2022-05-09 20:47:08 +02:00
baos . write ( encodeWorldClock ( clock ) ) ;
}
} else {
baos . write ( 0 ) ;
}
} catch ( final IOException e ) {
LOG . error ( " Unable to send world clocks to device " , e ) ;
return ;
}
2022-10-31 13:09:36 +01:00
writeToChunked2021 ( builder , ( short ) 0x0008 , baos . toByteArray ( ) , isWorldClocksEncrypted ( ) ) ;
}
protected boolean isWorldClocksEncrypted ( ) {
return false ;
2022-05-09 20:47:08 +02:00
}
2022-08-18 23:03:28 +02:00
private byte [ ] encodeWorldClock ( final WorldClock clock ) {
2022-05-09 20:47:08 +02:00
final DeviceCoordinator coordinator = DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream ( ) ;
final TimeZone timezone = TimeZone . getTimeZone ( clock . getTimeZoneId ( ) ) ;
final ZoneId zoneId = ZoneId . of ( clock . getTimeZoneId ( ) ) ;
2022-10-31 13:09:36 +01:00
// Usually the 3-letter city code (eg. LIS for Lisbon)
if ( clock . getCode ( ) ! = null ) {
baos . write ( StringUtils . truncate ( clock . getCode ( ) , 3 ) . toUpperCase ( ) . getBytes ( StandardCharsets . UTF_8 ) ) ;
} else {
baos . write ( StringUtils . truncate ( clock . getLabel ( ) , 3 ) . toUpperCase ( ) . getBytes ( StandardCharsets . UTF_8 ) ) ;
}
2022-05-09 20:47:08 +02:00
baos . write ( 0x00 ) ;
// Some other string? Seems to be empty
baos . write ( 0x00 ) ;
// The city name / label that shows up on the band
baos . write ( StringUtils . truncate ( clock . getLabel ( ) , coordinator . getWorldClocksLabelLength ( ) ) . getBytes ( StandardCharsets . UTF_8 ) ) ;
baos . write ( 0x00 ) ;
// The raw offset from UTC, in number of 15-minute blocks
baos . write ( ( int ) ( timezone . getRawOffset ( ) / ( 1000L * 60L * 15L ) ) ) ;
// Daylight savings
final boolean useDaylightTime = timezone . useDaylightTime ( ) ;
final boolean inDaylightTime = timezone . inDaylightTime ( new Date ( ) ) ;
byte daylightByte = 0 ;
// The daylight savings offset, either currently (the previous transition) or future (the next transition), in minutes
byte daylightOffsetMinutes = 0 ;
final ZoneRules zoneRules = zoneId . getRules ( ) ;
if ( useDaylightTime ) {
final ZoneOffsetTransition transition ;
if ( inDaylightTime ) {
daylightByte = 0x01 ;
transition = zoneRules . previousTransition ( Instant . now ( ) ) ;
} else {
daylightByte = 0x02 ;
transition = zoneRules . nextTransition ( Instant . now ( ) ) ;
}
daylightOffsetMinutes = ( byte ) transition . getDuration ( ) . toMinutes ( ) ;
}
baos . write ( daylightByte ) ;
baos . write ( daylightOffsetMinutes ) ;
// The timestamp of the next daylight savings transition, if any
final ZoneOffsetTransition nextTransition = zoneRules . nextTransition ( Instant . now ( ) ) ;
long nextTransitionTs = 0 ;
if ( nextTransition ! = null ) {
nextTransitionTs = nextTransition
. getDateTimeBefore ( )
. atZone ( zoneId )
. toEpochSecond ( ) ;
}
for ( int i = 0 ; i < 4 ; i + + ) {
baos . write ( ( byte ) ( ( nextTransitionTs > > ( i * 8 ) ) & 0xff ) ) ;
}
2022-10-31 13:09:36 +01:00
if ( coordinator . supportsDisabledWorldClocks ( ) ) {
baos . write ( ( byte ) ( clock . getEnabled ( ) ? 0x01 : 0x00 ) ) ;
}
2022-05-09 20:47:08 +02:00
return baos . toByteArray ( ) ;
} catch ( final IOException e ) {
throw new RuntimeException ( " This should never happen " , e ) ;
}
}
2016-07-25 00:00:22 +02:00
@Override
public void onSetTime ( ) {
try {
TransactionBuilder builder = performInitialized ( " Set date and time " ) ;
2016-12-01 22:49:58 +01:00
setCurrentTimeWithService ( builder ) ;
//TODO: once we have a common strategy for sending events (e.g. EventHandler), remove this call from here. Meanwhile it does no harm.
2020-02-19 09:41:50 +01:00
// = we should genaralize the pebble calender code
2022-08-18 23:03:28 +02:00
sendCalendarEvents ( builder ) ;
2016-07-25 00:00:22 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
2019-08-02 00:11:11 +02:00
LOG . error ( " Unable to set time on Huami device " , ex ) ;
2016-07-25 00:00:22 +02:00
}
}
@Override
public void onSetCallState ( CallSpec callSpec ) {
if ( callSpec . command = = CallSpec . CALL_INCOMING ) {
telephoneRinging = true ;
2019-11-21 13:24:06 +01:00
AbortTransactionAction abortAction = new StopNotificationAction ( getCharacteristic ( UUID_CHARACTERISTIC_ALERT_LEVEL ) ) {
2016-07-25 00:00:22 +02:00
@Override
protected boolean shouldAbort ( ) {
return ! isTelephoneRinging ( ) ;
}
} ;
2017-03-02 00:27:54 +01:00
String message = NotificationUtils . getPreferredTextFor ( callSpec ) ;
2017-10-22 00:02:36 +02:00
SimpleNotification simpleNotification = new SimpleNotification ( message , AlertCategory . IncomingCall , null ) ;
2018-08-02 10:55:30 +02:00
performPreferredNotification ( " incoming call " , MiBandConst . ORIGIN_INCOMING_CALL , simpleNotification , HuamiService . ALERT_LEVEL_PHONE_CALL , abortAction ) ;
2016-07-25 00:00:22 +02:00
} else if ( ( callSpec . command = = CallSpec . CALL_START ) | | ( callSpec . command = = CallSpec . CALL_END ) ) {
telephoneRinging = false ;
2019-08-29 08:32:29 +02:00
stopCurrentCallNotification ( ) ;
2017-03-14 00:44:59 +01:00
}
}
2021-01-06 16:28:20 +01:00
public void onSetCallStateNew ( CallSpec callSpec ) {
if ( callSpec . command = = CallSpec . CALL_INCOMING ) {
byte [ ] message = NotificationUtils . getPreferredTextFor ( callSpec ) . getBytes ( ) ;
int length = 10 + message . length ;
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( new byte [ ] { 3 , 0 , 0 , 0 , 0 , 0 } ) ;
buf . put ( message ) ;
buf . put ( new byte [ ] { 0 , 0 , 0 , 2 } ) ;
try {
TransactionBuilder builder = performInitialized ( " incoming call " ) ;
writeToChunked ( builder , 0 , buf . array ( ) ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
LOG . error ( " Unable to send incoming call " ) ;
}
} else if ( ( callSpec . command = = CallSpec . CALL_START ) | | ( callSpec . command = = CallSpec . CALL_END ) ) {
try {
TransactionBuilder builder = performInitialized ( " end call " ) ;
writeToChunked ( builder , 0 , new byte [ ] { 3 , 3 , 0 , 0 , 0 , 0 } ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
LOG . error ( " Unable to send end call " ) ;
}
}
}
2019-08-29 08:32:29 +02:00
private void stopCurrentCallNotification ( ) {
2017-03-14 00:44:59 +01:00
try {
TransactionBuilder builder = performInitialized ( " stop notification " ) ;
getNotificationStrategy ( ) . stopCurrentNotification ( builder ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
2019-08-29 08:32:29 +02:00
LOG . error ( " Error stopping call notification " ) ;
2016-07-25 00:00:22 +02:00
}
}
@Override
public void onSetCannedMessages ( CannedMessagesSpec cannedMessagesSpec ) {
2021-12-23 18:06:58 +01:00
if ( cannedMessagesSpec . type = = CannedMessagesSpec . TYPE_REJECTEDCALLS ) {
try {
TransactionBuilder builder = performInitialized ( " Set canned messages " ) ;
int handle = 0x12345678 ;
2022-02-02 12:57:25 +01:00
for ( int i = 0 ; i < 16 ; i + + ) {
byte [ ] delete_command = new byte [ ] { 0x07 , ( byte ) ( handle & 0xff ) , ( byte ) ( ( handle & 0xff00 ) > > 8 ) , ( byte ) ( ( handle & 0xff0000 ) > > 16 ) , ( byte ) ( ( handle & 0xff000000 ) > > 24 ) } ;
2022-08-18 23:03:28 +02:00
writeToChunked2021 ( builder , ( short ) 0x0013 , delete_command , false ) ;
2022-02-02 12:57:25 +01:00
handle + + ;
}
handle = 0x12345678 ;
2021-12-23 18:06:58 +01:00
for ( String cannedMessage : cannedMessagesSpec . cannedMessages ) {
2022-02-02 12:57:25 +01:00
int length = cannedMessage . getBytes ( ) . length + 6 ;
2021-12-23 18:06:58 +01:00
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 0x05 ) ; // create
buf . putInt ( handle + + ) ;
buf . put ( cannedMessage . getBytes ( ) ) ;
2022-02-02 12:57:25 +01:00
buf . put ( ( byte ) 0x00 ) ;
2022-08-18 23:03:28 +02:00
writeToChunked2021 ( builder , ( short ) 0x0013 , buf . array ( ) , false ) ;
2021-12-23 18:06:58 +01:00
}
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
2022-08-18 23:03:28 +02:00
LOG . error ( " Unable to set canned messages on Huami device " , ex ) ;
2021-12-23 18:06:58 +01:00
}
}
2016-07-25 00:00:22 +02:00
}
private boolean isTelephoneRinging ( ) {
// don't synchronize, this is not really important
return telephoneRinging ;
}
@Override
public void onSetMusicState ( MusicStateSpec stateSpec ) {
2018-08-07 12:44:00 +02:00
DeviceCoordinator coordinator = DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
if ( ! coordinator . supportsMusicInfo ( ) ) {
return ;
}
2020-05-20 05:05:00 +02:00
if ( stateSpec ! = null & & ! stateSpec . equals ( bufferMusicStateSpec ) ) {
2018-08-06 23:11:40 +02:00
bufferMusicStateSpec = stateSpec ;
2020-06-22 18:06:40 +02:00
if ( isMusicAppStarted ) {
sendMusicStateToDevice ( null , bufferMusicStateSpec ) ;
}
2018-08-07 12:44:00 +02:00
}
2016-07-25 00:00:22 +02:00
}
@Override
public void onSetMusicInfo ( MusicSpec musicSpec ) {
2018-08-07 12:44:00 +02:00
DeviceCoordinator coordinator = DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
if ( ! coordinator . supportsMusicInfo ( ) ) {
return ;
}
2020-05-20 05:05:00 +02:00
if ( musicSpec ! = null & & ! musicSpec . equals ( bufferMusicSpec ) ) {
2018-08-06 23:11:40 +02:00
bufferMusicSpec = musicSpec ;
2020-06-22 18:06:40 +02:00
if ( bufferMusicStateSpec ! = null ) {
bufferMusicStateSpec . state = 0 ;
bufferMusicStateSpec . position = 0 ;
}
2018-08-08 17:48:23 +02:00
if ( isMusicAppStarted ) {
2020-06-22 18:06:40 +02:00
sendMusicStateToDevice ( bufferMusicSpec , bufferMusicStateSpec ) ;
2018-08-08 17:48:23 +02:00
}
2018-08-07 12:44:00 +02:00
}
2018-08-06 23:11:40 +02:00
}
2022-08-18 23:03:28 +02:00
protected void onMusicAppOpen ( ) {
LOG . info ( " Music app started " ) ;
isMusicAppStarted = true ;
sendMusicStateToDevice ( ) ;
sendVolumeStateToDevice ( ) ;
}
protected void onMusicAppClosed ( ) {
LOG . info ( " Music app terminated " ) ;
isMusicAppStarted = false ;
}
2020-06-22 18:06:40 +02:00
private void sendMusicStateToDevice ( ) {
sendMusicStateToDevice ( bufferMusicSpec , bufferMusicStateSpec ) ;
}
2022-05-05 15:14:15 +02:00
@Override
public void onSetPhoneVolume ( final float volume ) {
if ( characteristicChunked = = null ) {
return ;
}
2022-08-18 23:03:28 +02:00
final byte [ ] volumeCommand = new byte [ ] { MUSIC_FLAG_VOLUME , ( byte ) Math . round ( volume ) } ;
2022-05-05 15:14:15 +02:00
try {
final TransactionBuilder builder = performInitialized ( " send volume " ) ;
writeToChunked ( builder , 3 , volumeCommand ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( final IOException e ) {
LOG . error ( " Unable to send volume " , e ) ;
}
LOG . info ( " sendVolumeStateToDevice: {} " , volume ) ;
}
private void sendVolumeStateToDevice ( ) {
2022-08-18 23:03:28 +02:00
onSetPhoneVolume ( getPhoneVolume ( ) ) ;
}
protected int getPhoneVolume ( ) {
2022-05-05 15:14:15 +02:00
final AudioManager audioManager = ( AudioManager ) getContext ( ) . getSystemService ( Context . AUDIO_SERVICE ) ;
final int volumeLevel = audioManager . getStreamVolume ( AudioManager . STREAM_MUSIC ) ;
final int volumeMax = audioManager . getStreamMaxVolume ( AudioManager . STREAM_MUSIC ) ;
final int volumePercentage = ( byte ) Math . round ( 100 * ( volumeLevel / ( float ) volumeMax ) ) ;
2022-08-18 23:03:28 +02:00
return volumePercentage ;
2022-05-05 15:14:15 +02:00
}
2022-08-18 23:03:28 +02:00
protected void sendMusicStateToDevice ( final MusicSpec musicSpec , final MusicStateSpec musicStateSpec ) {
2018-08-06 23:11:40 +02:00
if ( characteristicChunked = = null ) {
return ;
}
2020-06-22 18:06:40 +02:00
if ( musicStateSpec = = null ) {
2018-08-06 23:11:40 +02:00
return ;
}
2022-08-18 23:03:28 +02:00
try {
TransactionBuilder builder = performInitialized ( " send playback info " ) ;
writeToChunked ( builder , 3 , encodeMusicState ( musicSpec , musicStateSpec , false ) ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
LOG . error ( " Unable to send playback state " ) ;
}
LOG . info ( " sendMusicStateToDevice: {}, {} " , musicSpec , musicStateSpec ) ;
}
protected byte [ ] encodeMusicState ( final MusicSpec musicSpec , final MusicStateSpec musicStateSpec , final boolean includeVolume ) {
2022-05-24 00:52:58 +02:00
String artist = " " ;
String album = " " ;
String track = " " ;
2018-08-06 23:11:40 +02:00
byte flags = 0x00 ;
2022-08-18 23:03:28 +02:00
int length = 1 ;
if ( musicStateSpec ! = null ) {
length + = 4 ;
flags | = MUSIC_FLAG_STATE ;
}
if ( includeVolume ) {
length + = 1 ;
flags | = MUSIC_FLAG_VOLUME ;
}
2020-06-22 18:06:40 +02:00
if ( musicSpec ! = null ) {
2022-05-24 00:52:58 +02:00
artist = StringUtils . truncate ( musicSpec . artist , 80 ) ;
album = StringUtils . truncate ( musicSpec . album , 80 ) ;
2022-06-15 19:56:37 +02:00
track = StringUtils . truncate ( musicSpec . track , 80 ) ;
2022-05-24 00:52:58 +02:00
if ( artist . getBytes ( ) . length > 0 ) {
length + = artist . getBytes ( ) . length + 1 ;
2022-08-18 23:03:28 +02:00
flags | = MUSIC_FLAG_ARTIST ;
2020-06-22 18:06:40 +02:00
}
2022-05-24 00:52:58 +02:00
if ( album . getBytes ( ) . length > 0 ) {
length + = album . getBytes ( ) . length + 1 ;
2022-08-18 23:03:28 +02:00
flags | = MUSIC_FLAG_ALBUM ;
2020-06-22 18:06:40 +02:00
}
2022-05-24 00:52:58 +02:00
if ( track . getBytes ( ) . length > 0 ) {
length + = track . getBytes ( ) . length + 1 ;
2022-08-18 23:03:28 +02:00
flags | = MUSIC_FLAG_TRACK ;
2020-06-22 18:06:40 +02:00
}
if ( musicSpec . duration ! = 0 ) {
length + = 2 ;
2022-08-18 23:03:28 +02:00
flags | = MUSIC_FLAG_DURATION ;
2020-06-22 18:06:40 +02:00
}
2018-08-06 23:11:40 +02:00
}
2022-08-18 23:03:28 +02:00
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( flags ) ;
if ( musicStateSpec ! = null ) {
2018-08-06 23:11:40 +02:00
byte state ;
2020-06-22 18:06:40 +02:00
switch ( musicStateSpec . state ) {
2018-08-06 23:11:40 +02:00
case MusicStateSpec . STATE_PLAYING :
state = 1 ;
break ;
default :
state = 0 ;
}
buf . put ( state ) ;
2020-06-22 18:06:40 +02:00
buf . put ( ( byte ) 0 ) ;
buf . putShort ( ( short ) musicStateSpec . position ) ;
2022-08-18 23:03:28 +02:00
}
2018-08-06 23:11:40 +02:00
2022-08-18 23:03:28 +02:00
if ( musicSpec ! = null ) {
if ( artist . getBytes ( ) . length > 0 ) {
buf . put ( artist . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
2018-08-06 23:11:40 +02:00
}
2022-08-18 23:03:28 +02:00
if ( album . getBytes ( ) . length > 0 ) {
buf . put ( album . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
}
if ( track . getBytes ( ) . length > 0 ) {
buf . put ( track . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
}
if ( musicSpec . duration ! = 0 ) {
buf . putShort ( ( short ) musicSpec . duration ) ;
}
}
2018-08-06 23:11:40 +02:00
2022-08-18 23:03:28 +02:00
if ( includeVolume ) {
buf . put ( ( byte ) getPhoneVolume ( ) ) ;
2018-08-06 23:11:40 +02:00
}
2022-08-18 23:03:28 +02:00
return buf . array ( ) ;
2016-07-25 00:00:22 +02:00
}
@Override
2018-12-16 16:05:13 +01:00
public void onReset ( int flags ) {
2016-07-25 00:00:22 +02:00
try {
2018-12-16 16:05:13 +01:00
TransactionBuilder builder = performInitialized ( " Reset " ) ;
if ( ( flags & GBDeviceProtocol . RESET_FLAGS_FACTORY_RESET ) ! = 0 ) {
sendFactoryReset ( builder ) ;
} else {
sendReboot ( builder ) ;
}
2016-07-25 00:00:22 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
2018-12-16 16:05:13 +01:00
LOG . error ( " Unable to reset " , ex ) ;
2016-07-25 00:00:22 +02:00
}
}
2018-08-01 22:56:01 +02:00
public HuamiSupport sendReboot ( TransactionBuilder builder ) {
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_FIRMWARE ) , new byte [ ] { HuamiService . COMMAND_FIRMWARE_REBOOT } ) ;
2017-09-25 00:03:40 +02:00
return this ;
}
2018-12-16 16:05:13 +01:00
public HuamiSupport sendFactoryReset ( TransactionBuilder builder ) {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_FACTORY_RESET ) ;
2018-12-16 16:05:13 +01:00
return this ;
}
2016-07-25 00:00:22 +02:00
@Override
public void onHeartRateTest ( ) {
2017-10-23 10:28:54 +02:00
if ( characteristicHRControlPoint = = null ) {
return ;
}
2016-10-30 23:04:21 +01:00
try {
TransactionBuilder builder = performInitialized ( " HeartRateTest " ) ;
2018-11-05 23:27:29 +01:00
enableNotifyHeartRateMeasurements ( true , builder ) ;
2017-10-23 10:28:54 +02:00
builder . write ( characteristicHRControlPoint , stopHeartMeasurementContinuous ) ;
builder . write ( characteristicHRControlPoint , stopHeartMeasurementManual ) ;
builder . write ( characteristicHRControlPoint , startHeartMeasurementManual ) ;
2016-10-30 23:04:21 +01:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
2019-08-02 00:11:11 +02:00
LOG . error ( " Unable to read heart rate from Huami device " , ex ) ;
2016-07-25 00:00:22 +02:00
}
}
@Override
public void onEnableRealtimeHeartRateMeasurement ( boolean enable ) {
2017-10-23 10:28:54 +02:00
if ( characteristicHRControlPoint = = null ) {
return ;
}
2016-11-29 23:22:36 +01:00
try {
2017-10-23 10:28:54 +02:00
TransactionBuilder builder = performInitialized ( " Enable realtime heart rate measurement " ) ;
2018-11-05 23:27:29 +01:00
enableNotifyHeartRateMeasurements ( enable , builder ) ;
2016-11-29 23:22:36 +01:00
if ( enable ) {
2017-10-23 10:28:54 +02:00
builder . write ( characteristicHRControlPoint , stopHeartMeasurementManual ) ;
builder . write ( characteristicHRControlPoint , startHeartMeasurementContinuous ) ;
2016-11-29 23:22:36 +01:00
} else {
2017-11-11 23:36:38 +01:00
builder . write ( characteristicHRControlPoint , stopHeartMeasurementContinuous ) ;
2016-11-29 23:22:36 +01:00
}
builder . queue ( getQueue ( ) ) ;
enableRealtimeSamplesTimer ( enable ) ;
} catch ( IOException ex ) {
2017-10-23 10:28:54 +02:00
LOG . error ( " Unable to enable realtime heart rate measurement " , ex ) ;
2016-11-29 23:22:36 +01:00
}
2016-07-25 00:00:22 +02:00
}
2022-08-18 23:03:28 +02:00
protected void enableNotifyHeartRateMeasurements ( boolean enable , TransactionBuilder builder ) {
2018-11-05 23:27:29 +01:00
if ( heartRateNotifyEnabled ! = enable ) {
BluetoothGattCharacteristic heartrateCharacteristic = getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT ) ;
if ( heartrateCharacteristic ! = null ) {
builder . notify ( heartrateCharacteristic , enable ) ;
heartRateNotifyEnabled = enable ;
}
}
}
2016-07-25 00:00:22 +02:00
@Override
public void onFindDevice ( boolean start ) {
2022-09-19 14:46:02 +02:00
if ( findDeviceLoopTimer ! = null )
findDeviceLoopTimer . cancel ( ) ;
2016-07-25 00:00:22 +02:00
if ( start ) {
2022-09-19 14:46:02 +02:00
int loopInterval = getFindDeviceInterval ( ) ;
LOG . info ( " Sending find device, interval: " + loopInterval ) ;
findDeviceLoopTimer = new Timer ( " Huami Find Loop Timer " ) ;
findDeviceLoopTimer . scheduleAtFixedRate ( new TimerTask ( ) {
2016-07-25 00:00:22 +02:00
@Override
2022-09-19 14:46:02 +02:00
public void run ( ) {
sendFindDeviceCommand ( true ) ;
2016-07-25 00:00:22 +02:00
}
2022-09-19 14:46:02 +02:00
} , loopInterval , loopInterval ) ;
}
sendFindDeviceCommand ( start ) ;
}
protected int getFindDeviceInterval ( ) {
2022-10-22 21:53:45 +02:00
final VibrationProfile findBand = HuamiCoordinator . getVibrationProfile (
getDevice ( ) . getAddress ( ) ,
HuamiVibrationPatternNotificationType . FIND_BAND ,
supportsDeviceDefaultVibrationProfiles ( )
) ;
2022-09-19 14:46:02 +02:00
int findDeviceInterval = 0 ;
2022-10-22 21:53:45 +02:00
if ( findBand ! = null ) {
// It can be null if the device supports continuous find mode
// If that's the case, this function shouldn't even have been called
for ( int len : findBand . getOnOffSequence ( ) )
findDeviceInterval + = len ;
2022-09-19 14:46:02 +02:00
2022-10-22 21:53:45 +02:00
if ( findBand . getRepeat ( ) > 0 )
findDeviceInterval * = findBand . getRepeat ( ) ;
2022-09-19 14:46:02 +02:00
2022-10-22 21:53:45 +02:00
if ( findDeviceInterval > 10000 ) // 10 seconds, about as long as Mi Fit allows
findDeviceInterval = 10000 ;
} else {
2022-09-19 14:46:02 +02:00
findDeviceInterval = 10000 ;
2022-10-22 21:53:45 +02:00
}
2022-09-19 14:46:02 +02:00
return findDeviceInterval ;
}
2022-09-26 19:20:12 +02:00
protected void sendFindDeviceCommand ( boolean start ) {
2022-09-19 14:46:02 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( UUID_CHARACTERISTIC_ALERT_LEVEL ) ;
try {
TransactionBuilder builder = performInitialized ( " find huami " ) ;
builder . write ( characteristic , start ? new byte [ ] { 3 } : new byte [ ] { 0 } ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
LOG . error ( " error while sending find Huami device command " , e ) ;
2016-07-25 00:00:22 +02:00
}
}
@Override
2018-03-31 16:21:25 +02:00
public void onFetchRecordedData ( int dataTypes ) {
2023-05-20 21:44:18 +02:00
final HuamiCoordinator coordinator = getCoordinator ( ) ;
2023-05-21 00:34:36 +02:00
if ( ( dataTypes & RecordedDataTypes . TYPE_ACTIVITY ) ! = 0 ) {
this . fetchOperationQueue . add ( new FetchActivityOperation ( this ) ) ;
}
if ( ( dataTypes & RecordedDataTypes . TYPE_GPS_TRACKS ) ! = 0 & & coordinator . supportsActivityTracks ( ) ) {
this . fetchOperationQueue . add ( new FetchSportsSummaryOperation ( this , 1 ) ) ;
}
if ( ( dataTypes & RecordedDataTypes . TYPE_DEBUGLOGS ) ! = 0 & & coordinator . supportsDebugLogs ( ) ) {
this . fetchOperationQueue . add ( new HuamiFetchDebugLogsOperation ( this ) ) ;
}
2023-06-10 18:19:22 +02:00
if ( Huami2021Coordinator . experimentalFeatures ( getDevice ( ) ) ) {
if ( ( dataTypes & RecordedDataTypes . TYPE_SPO2 ) ! = 0 & & coordinator . supportsSpo2 ( ) ) {
this . fetchOperationQueue . add ( new FetchSpo2NormalOperation ( this ) ) ;
}
2023-05-21 00:34:36 +02:00
2023-06-10 18:19:22 +02:00
if ( ( dataTypes & RecordedDataTypes . TYPE_STRESS ) ! = 0 & & coordinator . supportsStressMeasurement ( ) ) {
this . fetchOperationQueue . add ( new FetchStressAutoOperation ( this ) ) ;
this . fetchOperationQueue . add ( new FetchStressManualOperation ( this ) ) ;
}
2023-05-21 00:34:36 +02:00
2023-06-10 18:19:22 +02:00
if ( ( dataTypes & RecordedDataTypes . TYPE_HEART_RATE ) ! = 0 & & coordinator . supportsHeartRateStats ( ) ) {
this . fetchOperationQueue . add ( new FetchHeartRateManualOperation ( this ) ) ;
this . fetchOperationQueue . add ( new FetchHeartRateMaxOperation ( this ) ) ;
this . fetchOperationQueue . add ( new FetchHeartRateRestingOperation ( this ) ) ;
}
2023-05-27 19:59:12 +02:00
2023-06-10 18:19:22 +02:00
if ( ( dataTypes & RecordedDataTypes . TYPE_PAI ) ! = 0 & & coordinator . supportsPai ( ) ) {
this . fetchOperationQueue . add ( new FetchPaiOperation ( this ) ) ;
}
2023-05-27 20:02:01 +02:00
2023-06-10 18:19:22 +02:00
if ( ( dataTypes & RecordedDataTypes . TYPE_SLEEP_RESPIRATORY_RATE ) ! = 0 & & coordinator . supportsSleepRespiratoryRate ( ) ) {
this . fetchOperationQueue . add ( new FetchSleepRespiratoryRateOperation ( this ) ) ;
}
2023-05-27 20:03:43 +02:00
}
2023-05-21 00:34:36 +02:00
final AbstractFetchOperation nextOperation = this . fetchOperationQueue . poll ( ) ;
if ( nextOperation ! = null ) {
try {
nextOperation . perform ( ) ;
} catch ( final IOException e ) {
LOG . error ( " Unable to fetch recorded data " , e ) ;
2023-05-20 21:44:18 +02:00
}
2016-11-18 21:14:04 +01:00
}
2016-07-25 00:00:22 +02:00
}
2023-05-21 00:34:36 +02:00
public AbstractFetchOperation getNextFetchOperation ( ) {
return fetchOperationQueue . poll ( ) ;
}
2016-07-25 00:00:22 +02:00
@Override
public void onEnableRealtimeSteps ( boolean enable ) {
2017-03-20 22:41:54 +01:00
try {
TransactionBuilder builder = performInitialized ( enable ? " Enabling realtime steps notifications " : " Disabling realtime steps notifications " ) ;
if ( enable ) {
2018-08-02 10:55:30 +02:00
builder . read ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_7_REALTIME_STEPS ) ) ;
2017-03-20 22:41:54 +01:00
}
2018-08-02 10:55:30 +02:00
builder . notify ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_7_REALTIME_STEPS ) , enable ) ;
2017-03-20 22:41:54 +01:00
builder . queue ( getQueue ( ) ) ;
enableRealtimeSamplesTimer ( enable ) ;
} catch ( IOException e ) {
LOG . error ( " Unable to change realtime steps notification to: " + enable , e ) ;
}
2016-07-25 00:00:22 +02:00
}
@Override
public void onInstallApp ( Uri uri ) {
2016-12-11 02:10:07 +01:00
try {
2019-07-25 20:51:28 +02:00
createUpdateFirmwareOperation ( uri ) . perform ( ) ;
2016-12-11 02:10:07 +01:00
} catch ( IOException ex ) {
GB . toast ( getContext ( ) , " Firmware cannot be installed: " + ex . getMessage ( ) , Toast . LENGTH_LONG , GB . ERROR , ex ) ;
}
2016-07-25 00:00:22 +02:00
}
2020-12-18 10:56:42 +01:00
// this could go though onion code with preferred notification, but I this should work on all huami devices
2019-11-21 13:24:06 +01:00
private void vibrateOnce ( ) {
BluetoothGattCharacteristic characteristic = getCharacteristic ( UUID_CHARACTERISTIC_ALERT_LEVEL ) ;
try {
TransactionBuilder builder = performInitialized ( " Vibrate once " ) ;
builder . write ( characteristic , new byte [ ] { 3 } ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
LOG . error ( " error while sending simple vibrate command " , e ) ;
}
}
2020-12-18 10:56:42 +01:00
private void processButtonAction ( ) {
2017-09-10 21:11:50 +02:00
if ( currentButtonTimerActivationTime ! = currentButtonPressTime ) {
return ;
}
2020-01-08 19:58:31 +01:00
//handle user events settings. 0 is long press, rest are button_id 1-3
switch ( currentButtonActionId ) {
case 0 :
2020-12-18 10:56:42 +01:00
executeButtonAction ( " button_long_press_action_selection " ) ;
2020-01-08 19:58:31 +01:00
break ;
case 1 :
2020-12-18 10:56:42 +01:00
executeButtonAction ( " button_single_press_action_selection " ) ;
2020-01-08 19:58:31 +01:00
break ;
case 2 :
2020-12-18 10:56:42 +01:00
executeButtonAction ( " button_double_press_action_selection " ) ;
2020-01-08 19:58:31 +01:00
break ;
case 3 :
2020-12-18 10:56:42 +01:00
executeButtonAction ( " button_triple_press_action_selection " ) ;
2020-01-08 19:58:31 +01:00
break ;
default :
break ;
}
2017-09-10 21:11:50 +02:00
2020-12-18 10:56:42 +01:00
currentButtonActionId = 0 ;
currentButtonPressCount = 0 ;
currentButtonPressTime = System . currentTimeMillis ( ) ;
}
2017-09-10 21:11:50 +02:00
2020-12-18 10:56:42 +01:00
private void executeButtonAction ( String buttonKey ) {
Prefs prefs = new Prefs ( GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ) ;
String buttonPreference = prefs . getString ( buttonKey , PREF_BUTTON_ACTION_SELECTION_OFF ) ;
2020-01-08 19:58:31 +01:00
2020-12-18 10:56:42 +01:00
if ( buttonPreference . equals ( PREF_BUTTON_ACTION_SELECTION_OFF ) ) {
return ;
}
2019-11-14 12:33:36 +01:00
if ( prefs . getBoolean ( HuamiConst . PREF_BUTTON_ACTION_VIBRATE , false ) ) {
2019-11-21 13:24:06 +01:00
vibrateOnce ( ) ;
2017-09-10 21:11:50 +02:00
}
2022-02-19 16:04:48 +01:00
switch ( buttonPreference ) {
case PREF_BUTTON_ACTION_SELECTION_BROADCAST :
sendSystemBroadcastWithButtonId ( ) ;
break ;
case PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_START :
OpenTracksController . startRecording ( this . getContext ( ) ) ;
break ;
case PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_STOP :
OpenTracksController . stopRecording ( this . getContext ( ) ) ;
break ;
2022-05-09 17:18:06 +02:00
case PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_TOGGLE :
OpenTracksController . toggleRecording ( this . getContext ( ) ) ;
break ;
2022-02-19 16:04:48 +01:00
default :
handleMediaButton ( buttonPreference ) ;
2020-12-18 10:56:42 +01:00
}
2017-09-10 21:11:50 +02:00
}
2022-08-18 23:03:28 +02:00
protected void handleDeviceAction ( String deviceAction , String message ) {
2020-08-16 22:07:55 +02:00
if ( deviceAction . equals ( PREF_DEVICE_ACTION_SELECTION_OFF ) ) {
return ;
}
2022-02-19 16:04:48 +01:00
switch ( deviceAction ) {
case PREF_BUTTON_ACTION_SELECTION_BROADCAST :
sendSystemBroadcast ( message ) ;
break ;
case PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_START :
OpenTracksController . startRecording ( this . getContext ( ) ) ;
break ;
case PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_STOP :
OpenTracksController . stopRecording ( this . getContext ( ) ) ;
break ;
2022-05-09 17:18:06 +02:00
case PREF_BUTTON_ACTION_SELECTION_FITNESS_APP_TOGGLE :
OpenTracksController . toggleRecording ( this . getContext ( ) ) ;
break ;
2022-02-19 16:04:48 +01:00
default :
handleMediaButton ( deviceAction ) ;
2020-08-16 22:07:55 +02:00
}
}
private void sendSystemBroadcast ( String message ) {
if ( message ! = null ) {
Intent in = new Intent ( ) ;
in . setAction ( message ) ;
LOG . info ( " Sending broadcast " + message ) ;
this . getContext ( ) . getApplicationContext ( ) . sendBroadcast ( in ) ;
}
}
2020-12-18 10:56:42 +01:00
private void sendSystemBroadcastWithButtonId ( ) {
Prefs prefs = new Prefs ( GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ) ;
String requiredButtonPressMessage = prefs . getString ( HuamiConst . PREF_BUTTON_ACTION_BROADCAST ,
this . getContext ( ) . getString ( R . string . mi2_prefs_button_press_broadcast_default_value ) ) ;
Intent in = new Intent ( ) ;
in . setAction ( requiredButtonPressMessage ) ;
in . putExtra ( " button_id " , currentButtonActionId ) ;
LOG . info ( " Sending " + requiredButtonPressMessage + " with button_id " + currentButtonActionId ) ;
this . getContext ( ) . getApplicationContext ( ) . sendBroadcast ( in ) ;
}
2020-01-08 19:58:31 +01:00
private void handleMediaButton ( String MediaAction ) {
2020-08-16 22:07:55 +02:00
if ( MediaAction . equals ( PREF_DEVICE_ACTION_SELECTION_OFF ) ) {
2020-01-08 19:58:31 +01:00
return ;
}
GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl ( ) ;
deviceEventMusicControl . event = GBDeviceEventMusicControl . Event . valueOf ( MediaAction ) ;
evaluateGBDeviceEvent ( deviceEventMusicControl ) ;
}
2019-05-25 22:19:19 +02:00
private void handleDeviceEvent ( byte [ ] value ) {
2018-08-02 22:35:02 +02:00
if ( value = = null | | value . length = = 0 ) {
2017-10-21 22:50:28 +02:00
return ;
}
GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl ( ) ;
switch ( value [ 0 ] ) {
case HuamiDeviceEvent . CALL_REJECT :
2019-05-25 22:19:19 +02:00
LOG . info ( " call rejected " ) ;
2017-10-21 22:50:28 +02:00
callCmd . event = GBDeviceEventCallControl . Event . REJECT ;
evaluateGBDeviceEvent ( callCmd ) ;
break ;
2017-11-19 23:46:24 +01:00
case HuamiDeviceEvent . CALL_IGNORE :
2019-05-25 22:19:19 +02:00
LOG . info ( " call ignored " ) ;
callCmd . event = GBDeviceEventCallControl . Event . IGNORE ;
evaluateGBDeviceEvent ( callCmd ) ;
2017-10-21 22:50:28 +02:00
break ;
case HuamiDeviceEvent . BUTTON_PRESSED :
LOG . info ( " button pressed " ) ;
handleButtonEvent ( ) ;
break ;
case HuamiDeviceEvent . BUTTON_PRESSED_LONG :
LOG . info ( " button long-pressed " ) ;
2020-01-08 19:58:31 +01:00
handleLongButtonEvent ( ) ;
2017-10-21 22:50:28 +02:00
break ;
case HuamiDeviceEvent . START_NONWEAR :
LOG . info ( " non-wear start detected " ) ;
2020-08-16 22:07:55 +02:00
processDeviceEvent ( HuamiDeviceEvent . START_NONWEAR ) ;
2017-10-21 22:50:28 +02:00
break ;
case HuamiDeviceEvent . ALARM_TOGGLED :
2022-01-20 10:09:23 +01:00
case HuamiDeviceEvent . ALARM_CHANGED :
LOG . info ( " An alarm was toggled or changed " ) ;
2019-12-27 22:19:17 +01:00
TransactionBuilder builder = new TransactionBuilder ( " requestAlarms " ) ;
requestAlarms ( builder ) ;
builder . queue ( getQueue ( ) ) ;
2017-10-21 22:50:28 +02:00
break ;
case HuamiDeviceEvent . FELL_ASLEEP :
LOG . info ( " Fell asleep " ) ;
2020-08-16 22:07:55 +02:00
processDeviceEvent ( HuamiDeviceEvent . FELL_ASLEEP ) ;
2017-10-21 22:50:28 +02:00
break ;
case HuamiDeviceEvent . WOKE_UP :
LOG . info ( " Woke up " ) ;
2020-08-16 22:07:55 +02:00
processDeviceEvent ( HuamiDeviceEvent . WOKE_UP ) ;
2017-10-21 22:50:28 +02:00
break ;
case HuamiDeviceEvent . STEPSGOAL_REACHED :
LOG . info ( " Steps goal reached " ) ;
break ;
case HuamiDeviceEvent . TICK_30MIN :
LOG . info ( " Tick 30 min (?) " ) ;
break ;
2017-11-19 23:46:24 +01:00
case HuamiDeviceEvent . FIND_PHONE_START :
2018-01-13 18:46:21 +01:00
LOG . info ( " find phone started " ) ;
2018-01-19 23:10:08 +01:00
acknowledgeFindPhone ( ) ; // FIXME: premature
findPhoneEvent . event = GBDeviceEventFindPhone . Event . START ;
evaluateGBDeviceEvent ( findPhoneEvent ) ;
2017-11-19 23:46:24 +01:00
break ;
case HuamiDeviceEvent . FIND_PHONE_STOP :
2018-01-13 18:46:21 +01:00
LOG . info ( " find phone stopped " ) ;
2018-01-19 23:10:08 +01:00
findPhoneEvent . event = GBDeviceEventFindPhone . Event . STOP ;
evaluateGBDeviceEvent ( findPhoneEvent ) ;
2017-11-19 23:46:24 +01:00
break ;
2018-08-02 22:35:02 +02:00
case HuamiDeviceEvent . MUSIC_CONTROL :
LOG . info ( " got music control " ) ;
GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl ( ) ;
switch ( value [ 1 ] ) {
case 0 :
deviceEventMusicControl . event = GBDeviceEventMusicControl . Event . PLAY ;
break ;
case 1 :
deviceEventMusicControl . event = GBDeviceEventMusicControl . Event . PAUSE ;
break ;
case 3 :
deviceEventMusicControl . event = GBDeviceEventMusicControl . Event . NEXT ;
break ;
case 4 :
deviceEventMusicControl . event = GBDeviceEventMusicControl . Event . PREVIOUS ;
break ;
case 5 :
deviceEventMusicControl . event = GBDeviceEventMusicControl . Event . VOLUMEUP ;
break ;
case 6 :
deviceEventMusicControl . event = GBDeviceEventMusicControl . Event . VOLUMEDOWN ;
break ;
2018-08-06 23:11:40 +02:00
case ( byte ) 224 :
2022-08-18 23:03:28 +02:00
onMusicAppOpen ( ) ;
2018-08-06 23:11:40 +02:00
break ;
case ( byte ) 225 :
2022-08-18 23:03:28 +02:00
onMusicAppClosed ( ) ;
2018-08-06 23:11:40 +02:00
break ;
2018-08-02 22:35:02 +02:00
default :
LOG . info ( " unhandled music control event " + value [ 1 ] ) ;
return ;
}
evaluateGBDeviceEvent ( deviceEventMusicControl ) ;
break ;
2019-12-26 23:05:13 +01:00
case HuamiDeviceEvent . MTU_REQUEST :
int mtu = ( value [ 2 ] & 0xff ) < < 8 | value [ 1 ] & 0xff ;
LOG . info ( " device announced MTU of " + mtu ) ;
2022-10-22 21:53:45 +02:00
setMtu ( mtu ) ;
2019-12-26 23:05:13 +01:00
/ *
* not really sure if this would make sense , is this event already a proof of a successful MTU
* negotiation initiated by the Huami device , and acknowledged by the phone ? do we really have to
* requestMTU ( ) from our side after receiving this ?
* /
if ( mMTU ! = mtu ) {
requestMTU ( mtu ) ;
}
* /
2022-06-04 22:20:28 +02:00
break ;
case HuamiDeviceEvent . WORKOUT_STARTING :
final HuamiWorkoutTrackActivityType activityType = HuamiWorkoutTrackActivityType . fromCode ( value [ 3 ] ) ;
2022-09-18 12:04:50 +02:00
final int activityKind ;
2022-06-04 22:20:28 +02:00
if ( activityType = = null ) {
2022-08-18 23:03:28 +02:00
LOG . warn ( " Unknown workout activity type {} " , String . format ( " 0x%02x " , value [ 3 ] ) ) ;
2022-09-18 12:04:50 +02:00
activityKind = ActivityKind . TYPE_UNKNOWN ;
} else {
activityKind = activityType . toActivityKind ( ) ;
2022-06-04 22:20:28 +02:00
}
2022-08-18 23:03:28 +02:00
final boolean needsGps = value [ 2 ] = = 1 ;
2022-06-04 22:20:28 +02:00
2022-08-18 23:03:28 +02:00
LOG . info ( " Workout starting on band: {}, needs gps = {} " , activityType , needsGps ) ;
2022-06-04 22:20:28 +02:00
2022-09-18 12:04:50 +02:00
onWorkoutOpen ( needsGps , activityKind ) ;
2022-06-04 22:20:28 +02:00
2019-12-26 23:05:13 +01:00
break ;
2017-10-21 22:50:28 +02:00
default :
2022-08-18 23:03:28 +02:00
LOG . warn ( " unhandled event {} " , String . format ( " 0x%02x " , value [ 0 ] ) ) ;
2022-06-04 22:20:28 +02:00
}
}
/ * *
* Track whether the currently selected workout needs gps ( received in { @link # handleDeviceEvent } , so we can start the activity tracking
* if needed in { @link # handleDeviceWorkoutEvent } , since in there we don ' t know what ' s the current workout .
* /
private boolean workoutNeedsGps = false ;
2022-09-18 12:04:50 +02:00
/ * *
* Track the { @link nodomain . freeyourgadget . gadgetbridge . model . ActivityKind } that was opened , for the same reasons as { @code workoutNeedsGps } .
* /
private int workoutActivityKind = ActivityKind . TYPE_UNKNOWN ;
2022-06-04 22:20:28 +02:00
/ * *
* Track the last time we actually sent a gps location . We need to signal that GPS as re - acquired if the last update was too long ago .
* /
private long lastPhoneGpsSent = 0 ;
2022-09-18 12:04:50 +02:00
protected void onWorkoutOpen ( final boolean needsGps , final int activityKind ) {
2022-08-18 23:03:28 +02:00
this . workoutNeedsGps = needsGps ;
2022-09-18 12:04:50 +02:00
this . workoutActivityKind = activityKind ;
2022-08-18 23:03:28 +02:00
final boolean sendGpsToBand = HuamiCoordinator . getWorkoutSendGpsToBand ( getDevice ( ) . getAddress ( ) ) ;
if ( workoutNeedsGps ) {
if ( sendGpsToBand ) {
lastPhoneGpsSent = 0 ;
sendPhoneGps ( HuamiPhoneGpsStatus . SEARCHING , null ) ;
GBLocationManager . start ( getContext ( ) , this ) ;
} else {
sendPhoneGps ( HuamiPhoneGpsStatus . DISABLED , null ) ;
}
}
}
protected void onWorkoutStart ( ) {
final boolean startOnPhone = HuamiCoordinator . getWorkoutStartOnPhone ( getDevice ( ) . getAddress ( ) ) ;
if ( workoutNeedsGps & & startOnPhone ) {
LOG . info ( " Starting OpenTracks recording " ) ;
2022-09-18 12:04:50 +02:00
OpenTracksController . startRecording ( getContext ( ) , workoutActivityKind ) ;
2022-08-18 23:03:28 +02:00
}
}
protected void onWorkoutEnd ( ) {
final boolean startOnPhone = HuamiCoordinator . getWorkoutStartOnPhone ( getDevice ( ) . getAddress ( ) ) ;
GBLocationManager . stop ( getContext ( ) , this ) ;
if ( startOnPhone ) {
LOG . info ( " Stopping OpenTracks recording " ) ;
OpenTracksController . stopRecording ( getContext ( ) ) ;
}
}
2022-06-04 22:20:28 +02:00
private void handleDeviceWorkoutEvent ( byte [ ] value ) {
if ( value = = null | | value . length = = 0 ) {
return ;
}
switch ( value [ 0 ] ) {
case 0x11 :
final HuamiWorkoutStatus status = HuamiWorkoutStatus . fromCode ( value [ 1 ] ) ;
LOG . info ( " Got workout status {} " , status ) ;
switch ( status ) {
case Start :
2022-08-18 23:03:28 +02:00
onWorkoutStart ( ) ;
2022-06-04 22:20:28 +02:00
break ;
case End :
2022-08-18 23:03:28 +02:00
onWorkoutEnd ( ) ;
break ;
default :
LOG . warn ( " Unknown workout status {} " , String . format ( " 0x%02x " , value [ 1 ] ) ) ;
2022-06-04 22:20:28 +02:00
break ;
}
break ;
default :
2022-08-18 23:03:28 +02:00
LOG . warn ( " Unhandled workout event {} " , String . format ( " 0x%02x " , value [ 0 ] ) ) ;
2022-06-04 22:20:28 +02:00
}
}
@Override
public void onSetGpsLocation ( final Location location ) {
final boolean sendGpsToBand = HuamiCoordinator . getWorkoutSendGpsToBand ( getDevice ( ) . getAddress ( ) ) ;
if ( ! sendGpsToBand ) {
LOG . warn ( " Sending GPS to band is disabled, ignoring location update " ) ;
return ;
}
boolean newGpsLock = System . currentTimeMillis ( ) - lastPhoneGpsSent > 5000 ;
lastPhoneGpsSent = System . currentTimeMillis ( ) ;
2022-08-18 23:03:28 +02:00
final HuamiPhoneGpsStatus status = newGpsLock ? HuamiPhoneGpsStatus . ACQUIRED : null ;
2022-06-04 22:20:28 +02:00
2022-08-18 23:03:28 +02:00
sendPhoneGps ( status , location ) ;
}
2022-06-04 22:20:28 +02:00
2022-08-18 23:03:28 +02:00
protected void sendPhoneGps ( final HuamiPhoneGpsStatus status , final Location location ) {
if ( characteristicChunked = = null | | location = = null ) {
return ;
2017-10-21 22:50:28 +02:00
}
2022-06-04 22:20:28 +02:00
2022-08-18 23:03:28 +02:00
final byte [ ] locationBytes = encodePhoneGpsPayload ( status , location ) ;
2022-06-04 22:20:28 +02:00
2022-08-18 23:03:28 +02:00
final ByteBuffer buf = ByteBuffer . allocate ( 1 + locationBytes . length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 0x06 ) ;
buf . put ( locationBytes ) ;
2022-06-04 22:20:28 +02:00
try {
final TransactionBuilder builder = performInitialized ( " send phone gps location " ) ;
writeToChunked ( builder , 6 , buf . array ( ) ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( final IOException e ) {
LOG . error ( " Unable to send location " , e ) ;
}
}
2022-08-18 23:03:28 +02:00
protected byte [ ] encodePhoneGpsPayload ( final HuamiPhoneGpsStatus status , final Location location ) {
int flags = 0 ;
int length = 4 ; // Start with just the flag bytes
2022-06-04 22:20:28 +02:00
2022-08-18 23:03:28 +02:00
if ( status ! = null ) {
flags | = WORKOUT_GPS_FLAG_STATUS ;
length + = 1 ;
}
if ( location ! = null ) {
flags | = WORKOUT_GPS_FLAG_POSITION ;
length + = 31 ;
}
final ByteBuffer buf = ByteBuffer . allocate ( length ) ;
2022-06-04 22:20:28 +02:00
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
2022-08-18 23:03:28 +02:00
2022-06-04 22:20:28 +02:00
buf . putInt ( flags ) ;
2022-08-18 23:03:28 +02:00
if ( status ! = null ) {
buf . put ( status . getCode ( ) ) ;
}
2022-06-04 22:20:28 +02:00
2022-08-18 23:03:28 +02:00
if ( location ! = null ) {
buf . putInt ( ( int ) ( location . getLongitude ( ) * 3000000 . 0 ) ) ;
buf . putInt ( ( int ) ( location . getLatitude ( ) * 3000000 . 0 ) ) ;
buf . putInt ( ( int ) location . getSpeed ( ) * 10 ) ;
buf . putInt ( ( int ) ( location . getAltitude ( ) * 100 ) ) ;
buf . putLong ( location . getTime ( ) ) ;
// Seems to always be ff ?
buf . putInt ( 0xffffffff ) ;
// Not sure what this is, maybe bearing? It changes while moving, but
// doesn't seem to be needed on the Mi Band 5
buf . putShort ( ( short ) 0x00 ) ;
// Seems to always be 0 ?
buf . put ( ( byte ) 0x00 ) ;
2022-06-04 22:20:28 +02:00
}
2022-08-18 23:03:28 +02:00
return buf . array ( ) ;
2017-10-21 22:50:28 +02:00
}
2022-08-18 23:03:28 +02:00
protected void requestMTU ( int mtu ) {
2022-06-04 22:20:28 +02:00
new TransactionBuilder ( " requestMtu " )
. requestMtu ( mtu )
. queue ( getQueue ( ) ) ;
mMTU = mtu ;
2019-12-26 23:05:13 +01:00
}
2022-07-19 15:26:25 +02:00
@Override
public void onMtuChanged ( BluetoothGatt gatt , int mtu , int status ) {
super . onMtuChanged ( gatt , mtu , status ) ;
LOG . info ( " MTU changed to {} " , mtu ) ;
2022-10-22 21:53:45 +02:00
setMtu ( mtu ) ;
2022-07-19 15:26:25 +02:00
}
2022-08-18 23:03:28 +02:00
protected void acknowledgeFindPhone ( ) {
2018-01-13 18:46:21 +01:00
try {
TransactionBuilder builder = performInitialized ( " acknowledge find phone " ) ;
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , AmazfitBipService . COMMAND_ACK_FIND_PHONE_IN_PROGRESS ) ;
2018-01-13 18:46:21 +01:00
builder . queue ( getQueue ( ) ) ;
} catch ( Exception ex ) {
2021-09-03 15:50:42 +02:00
LOG . error ( " Error while ending acknowledge find phone " , ex ) ;
2018-01-13 18:46:21 +01:00
}
}
2022-08-18 23:03:28 +02:00
protected void processDeviceEvent ( int event ) {
2020-08-16 22:07:55 +02:00
LOG . debug ( " Handling device event: " + event ) ;
Prefs prefs = new Prefs ( GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ) ;
String deviceActionBroadcastMessage = null ;
switch ( event ) {
case HuamiDeviceEvent . WOKE_UP :
String wakeupAction = prefs . getString ( PREF_DEVICE_ACTION_WOKE_UP_SELECTION , PREF_DEVICE_ACTION_SELECTION_OFF ) ;
if ( wakeupAction . equals ( PREF_DEVICE_ACTION_SELECTION_OFF ) ) return ;
deviceActionBroadcastMessage = prefs . getString ( PREF_DEVICE_ACTION_WOKE_UP_BROADCAST ,
this . getContext ( ) . getString ( R . string . prefs_events_forwarding_wokeup_broadcast_default_value ) ) ;
handleDeviceAction ( wakeupAction , deviceActionBroadcastMessage ) ;
break ;
case HuamiDeviceEvent . FELL_ASLEEP :
String fellsleepAction = prefs . getString ( PREF_DEVICE_ACTION_FELL_SLEEP_SELECTION , PREF_DEVICE_ACTION_SELECTION_OFF ) ;
if ( fellsleepAction . equals ( PREF_DEVICE_ACTION_SELECTION_OFF ) ) return ;
deviceActionBroadcastMessage = prefs . getString ( PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST ,
this . getContext ( ) . getString ( R . string . prefs_events_forwarding_fellsleep_broadcast_default_value ) ) ;
handleDeviceAction ( fellsleepAction , deviceActionBroadcastMessage ) ;
break ;
case HuamiDeviceEvent . START_NONWEAR :
String nonwearAction = prefs . getString ( PREF_DEVICE_ACTION_START_NON_WEAR_SELECTION , PREF_DEVICE_ACTION_SELECTION_OFF ) ;
if ( nonwearAction . equals ( PREF_DEVICE_ACTION_SELECTION_OFF ) ) return ;
deviceActionBroadcastMessage = prefs . getString ( PREF_DEVICE_ACTION_START_NON_WEAR_BROADCAST ,
this . getContext ( ) . getString ( R . string . prefs_events_forwarding_startnonwear_broadcast_default_value ) ) ;
handleDeviceAction ( nonwearAction , deviceActionBroadcastMessage ) ;
break ;
}
}
2020-01-08 19:58:31 +01:00
private void handleLongButtonEvent ( ) {
Prefs prefs = new Prefs ( GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ) ;
if ( ! prefs . getBoolean ( HuamiConst . PREF_BUTTON_ACTION_ENABLE , false ) ) {
return ;
}
currentButtonActionId = 0 ;
currentButtonPressTime = System . currentTimeMillis ( ) ;
currentButtonTimerActivationTime = currentButtonPressTime ;
2020-12-18 10:56:42 +01:00
processButtonAction ( ) ;
2020-01-08 19:58:31 +01:00
}
2019-11-14 12:33:36 +01:00
private void handleButtonEvent ( ) {
2017-09-10 21:11:50 +02:00
// If disabled we return from function immediately
2019-11-14 12:33:36 +01:00
Prefs prefs = new Prefs ( GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ) ;
if ( ! prefs . getBoolean ( HuamiConst . PREF_BUTTON_ACTION_ENABLE , false ) ) {
2017-09-10 21:11:50 +02:00
return ;
}
2019-11-14 12:33:36 +01:00
int buttonPressMaxDelay = prefs . getInt ( HuamiConst . PREF_BUTTON_ACTION_PRESS_MAX_INTERVAL , 2000 ) ;
int requiredButtonPressCount = prefs . getInt ( HuamiConst . PREF_BUTTON_ACTION_PRESS_COUNT , 0 ) ;
2017-09-10 21:11:50 +02:00
if ( requiredButtonPressCount > 0 ) {
long timeSinceLastPress = System . currentTimeMillis ( ) - currentButtonPressTime ;
if ( ( currentButtonPressTime = = 0 ) | | ( timeSinceLastPress < buttonPressMaxDelay ) ) {
currentButtonPressCount + + ;
2020-02-18 22:20:57 +01:00
} else {
2017-09-10 21:11:50 +02:00
currentButtonPressCount = 1 ;
currentButtonActionId = 0 ;
}
2020-01-08 19:58:31 +01:00
if ( buttonActionTimer ! = null ) {
buttonActionTimer . cancel ( ) ;
}
2017-09-10 21:11:50 +02:00
currentButtonPressTime = System . currentTimeMillis ( ) ;
if ( currentButtonPressCount = = requiredButtonPressCount ) {
currentButtonTimerActivationTime = currentButtonPressTime ;
2020-01-08 19:58:31 +01:00
LOG . info ( " Activating button timer " ) ;
buttonActionTimer = new Timer ( " Huami Button Action Timer " ) ;
buttonActionTimer . scheduleAtFixedRate ( new TimerTask ( ) {
@Override
public void run ( ) {
2020-12-18 10:56:42 +01:00
processButtonAction ( ) ;
2020-01-08 19:58:31 +01:00
buttonActionTimer . cancel ( ) ;
}
} , buttonPressMaxDelay , buttonPressMaxDelay ) ;
2017-09-10 21:11:50 +02:00
currentButtonActionId + + ;
currentButtonPressCount = 0 ;
}
}
}
2020-01-08 19:58:31 +01:00
2016-07-25 00:00:22 +02:00
@Override
2016-07-28 23:04:37 +02:00
public boolean onCharacteristicChanged ( BluetoothGatt gatt ,
BluetoothGattCharacteristic characteristic ) {
2016-07-25 00:00:22 +02:00
super . onCharacteristicChanged ( gatt , characteristic ) ;
UUID characteristicUUID = characteristic . getUuid ( ) ;
2018-08-02 10:55:30 +02:00
if ( HuamiService . UUID_CHARACTERISTIC_6_BATTERY_INFO . equals ( characteristicUUID ) ) {
2016-07-25 00:00:22 +02:00
handleBatteryInfo ( characteristic . getValue ( ) , BluetoothGatt . GATT_SUCCESS ) ;
2016-07-28 23:04:37 +02:00
return true ;
2016-07-25 00:00:22 +02:00
} else if ( MiBandService . UUID_CHARACTERISTIC_REALTIME_STEPS . equals ( characteristicUUID ) ) {
handleRealtimeSteps ( characteristic . getValue ( ) ) ;
2016-07-28 23:04:37 +02:00
return true ;
2017-03-05 21:45:39 +01:00
} else if ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT . equals ( characteristicUUID ) ) {
2016-07-25 00:00:22 +02:00
handleHeartrate ( characteristic . getValue ( ) ) ;
2016-07-28 23:04:37 +02:00
return true ;
2018-08-02 10:55:30 +02:00
} else if ( HuamiService . UUID_CHARACTERISTIC_AUTH . equals ( characteristicUUID ) ) {
2016-09-20 23:09:42 +02:00
LOG . info ( " AUTHENTICATION?? " + characteristicUUID ) ;
2016-09-13 12:15:03 +02:00
logMessageContent ( characteristic . getValue ( ) ) ;
2016-09-20 23:09:42 +02:00
return true ;
2018-08-02 10:55:30 +02:00
} else if ( HuamiService . UUID_CHARACTERISTIC_DEVICEEVENT . equals ( characteristicUUID ) ) {
2017-10-21 22:50:28 +02:00
handleDeviceEvent ( characteristic . getValue ( ) ) ;
2017-02-21 21:41:21 +01:00
return true ;
2022-06-04 22:20:28 +02:00
} else if ( HuamiService . UUID_CHARACTERISTIC_WORKOUT . equals ( characteristicUUID ) ) {
handleDeviceWorkoutEvent ( characteristic . getValue ( ) ) ;
return true ;
2018-08-02 10:55:30 +02:00
} else if ( HuamiService . UUID_CHARACTERISTIC_7_REALTIME_STEPS . equals ( characteristicUUID ) ) {
2017-03-20 22:41:54 +01:00
handleRealtimeSteps ( characteristic . getValue ( ) ) ;
return true ;
2019-12-27 22:19:17 +01:00
} else if ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION . equals ( characteristicUUID ) ) {
handleConfigurationInfo ( characteristic . getValue ( ) ) ;
return true ;
2023-06-11 16:45:41 +02:00
} else if ( HuamiService . UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ . equals ( characteristicUUID ) ) {
handleChunked ( characteristic . getValue ( ) ) ;
2022-02-02 12:57:25 +01:00
return true ;
2022-09-10 01:32:57 +02:00
} else if ( HuamiService . UUID_CHARACTERISTIC_RAW_SENSOR_DATA . equals ( characteristicUUID ) ) {
handleRawSensorData ( characteristic . getValue ( ) ) ;
return true ;
2016-07-25 00:00:22 +02:00
} else {
LOG . info ( " Unhandled characteristic changed: " + characteristicUUID ) ;
logMessageContent ( characteristic . getValue ( ) ) ;
}
2017-09-05 22:37:41 +02:00
2017-09-10 21:11:50 +02:00
return false ;
2017-02-21 21:41:21 +01:00
}
2016-07-25 00:00:22 +02:00
@Override
2016-07-28 23:04:37 +02:00
public boolean onCharacteristicRead ( BluetoothGatt gatt ,
BluetoothGattCharacteristic characteristic , int status ) {
2016-07-25 00:00:22 +02:00
super . onCharacteristicRead ( gatt , characteristic , status ) ;
UUID characteristicUUID = characteristic . getUuid ( ) ;
2021-01-19 01:34:58 +01:00
if ( GattCharacteristic . UUID_CHARACTERISTIC_DEVICE_NAME . equals ( characteristicUUID ) ) {
2016-07-25 00:00:22 +02:00
handleDeviceName ( characteristic . getValue ( ) , status ) ;
2016-07-28 23:04:37 +02:00
return true ;
2018-08-02 10:55:30 +02:00
} else if ( HuamiService . UUID_CHARACTERISTIC_6_BATTERY_INFO . equals ( characteristicUUID ) ) {
2016-07-25 00:00:22 +02:00
handleBatteryInfo ( characteristic . getValue ( ) , status ) ;
2016-07-28 23:04:37 +02:00
return true ;
2017-03-05 21:45:39 +01:00
} else if ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT . equals ( characteristicUUID ) ) {
2016-07-25 00:00:22 +02:00
logHeartrate ( characteristic . getValue ( ) , status ) ;
2016-07-28 23:04:37 +02:00
return true ;
2018-08-02 10:55:30 +02:00
} else if ( HuamiService . UUID_CHARACTERISTIC_7_REALTIME_STEPS . equals ( characteristicUUID ) ) {
2017-03-20 22:41:54 +01:00
handleRealtimeSteps ( characteristic . getValue ( ) ) ;
return true ;
2018-08-02 10:55:30 +02:00
} else if ( HuamiService . UUID_CHARACTERISTIC_DEVICEEVENT . equals ( characteristicUUID ) ) {
2017-10-21 22:50:28 +02:00
handleDeviceEvent ( characteristic . getValue ( ) ) ;
2017-02-21 21:41:21 +01:00
return true ;
2022-06-04 22:20:28 +02:00
} else if ( HuamiService . UUID_CHARACTERISTIC_WORKOUT . equals ( characteristicUUID ) ) {
handleDeviceWorkoutEvent ( characteristic . getValue ( ) ) ;
return true ;
2016-07-25 00:00:22 +02:00
} else {
LOG . info ( " Unhandled characteristic read: " + characteristicUUID ) ;
logMessageContent ( characteristic . getValue ( ) ) ;
}
2017-09-10 21:11:50 +02:00
2016-07-28 23:04:37 +02:00
return false ;
2016-07-25 00:00:22 +02:00
}
@Override
2016-07-28 23:04:37 +02:00
public boolean onCharacteristicWrite ( BluetoothGatt gatt ,
BluetoothGattCharacteristic characteristic , int status ) {
2016-07-25 00:00:22 +02:00
UUID characteristicUUID = characteristic . getUuid ( ) ;
2018-08-02 10:55:30 +02:00
if ( HuamiService . UUID_CHARACTERISTIC_AUTH . equals ( characteristicUUID ) ) {
2016-09-13 12:15:03 +02:00
LOG . info ( " KEY AES SEND " ) ;
logMessageContent ( characteristic . getValue ( ) ) ;
return true ;
2016-07-25 00:00:22 +02:00
}
2016-07-28 23:04:37 +02:00
return false ;
2016-07-25 00:00:22 +02:00
}
public void logHeartrate ( byte [ ] value , int status ) {
if ( status = = BluetoothGatt . GATT_SUCCESS & & value ! = null ) {
LOG . info ( " Got heartrate: " ) ;
2016-11-29 23:22:36 +01:00
if ( value . length = = 2 & & value [ 0 ] = = 0 ) {
2016-07-25 00:00:22 +02:00
int hrValue = ( value [ 1 ] & 0xff ) ;
GB . toast ( getContext ( ) , " Heart Rate measured: " + hrValue , Toast . LENGTH_LONG , GB . INFO ) ;
}
return ;
}
logMessageContent ( value ) ;
}
2022-09-04 21:43:39 +02:00
protected void handleHeartrate ( byte [ ] value ) {
2016-11-29 23:22:36 +01:00
if ( value . length = = 2 & & value [ 0 ] = = 0 ) {
2016-07-25 00:00:22 +02:00
int hrValue = ( value [ 1 ] & 0xff ) ;
if ( LOG . isDebugEnabled ( ) ) {
LOG . debug ( " heart rate: " + hrValue ) ;
}
2016-11-29 23:22:36 +01:00
RealtimeSamplesSupport realtimeSamplesSupport = getRealtimeSamplesSupport ( ) ;
realtimeSamplesSupport . setHeartrateBpm ( hrValue ) ;
if ( ! realtimeSamplesSupport . isRunning ( ) ) {
// single shot measurement, manually invoke storage and result publishing
realtimeSamplesSupport . triggerCurrentSample ( ) ;
}
2016-07-25 00:00:22 +02:00
}
}
2022-08-18 23:03:28 +02:00
protected void handleRealtimeSteps ( byte [ ] value ) {
2017-03-20 22:41:54 +01:00
if ( value = = null ) {
LOG . error ( " realtime steps: value is null " ) ;
return ;
}
if ( value . length = = 13 ) {
byte [ ] stepsValue = new byte [ ] { value [ 1 ] , value [ 2 ] } ;
int steps = BLETypeConversions . toUint16 ( stepsValue ) ;
if ( LOG . isDebugEnabled ( ) ) {
LOG . debug ( " realtime steps: " + steps ) ;
}
getRealtimeSamplesSupport ( ) . setSteps ( steps ) ;
} else {
LOG . warn ( " Unrecognized realtime steps value: " + Logging . formatBytes ( value ) ) ;
2016-07-25 00:00:22 +02:00
}
2016-11-29 23:22:36 +01:00
}
2022-05-15 18:52:01 +02:00
byte [ ] reassemblyBuffer ;
byte reassemblyType = 0x00 ;
2022-01-21 12:48:36 +01:00
2019-12-27 22:19:17 +01:00
private void handleConfigurationInfo ( byte [ ] value ) {
if ( value = = null | | value . length < 4 ) {
return ;
}
if ( value [ 0 ] = = 0x10 & & value [ 2 ] = = 0x01 ) {
2022-05-15 18:52:01 +02:00
if ( value [ 1 ] = = COMMAND_GPS_VERSION ) {
2019-12-27 22:19:17 +01:00
String gpsVersion = new String ( value , 3 , value . length - 3 ) ;
LOG . info ( " got gps version = " + gpsVersion ) ;
gbDevice . setFirmwareVersion2 ( gpsVersion ) ;
2022-05-15 18:52:01 +02:00
} else if ( value [ 1 ] = = COMMAND_ALARMS ) {
2019-12-27 22:19:17 +01:00
LOG . info ( " got alarms from watch " ) ;
2022-01-20 10:09:23 +01:00
decodeAndUpdateAlarmStatus ( value , false ) ;
2019-12-27 22:19:17 +01:00
} else {
LOG . warn ( " got configuration info we do not handle yet " + GB . hexdump ( value , 3 , - 1 ) ) ;
}
2022-01-21 12:48:36 +01:00
} else if ( value [ 0 ] = = ( ( byte ) 0x80 ) & & value [ 1 ] = = 0x01 ) {
boolean done = false ;
2022-05-15 18:52:01 +02:00
if ( value [ 2 ] = = 0x00 | | value [ 2 ] = = ( byte ) 0xc0 ) { // first chunk or complete data
reassemblyBuffer = new byte [ value . length - 8 ] ;
reassemblyType = value [ 4 ] ;
System . arraycopy ( value , 8 , reassemblyBuffer , 0 , reassemblyBuffer . length ) ;
2022-01-21 12:48:36 +01:00
if ( value [ 2 ] = = ( byte ) 0xc0 ) {
done = true ;
}
2022-05-15 18:52:01 +02:00
} else if ( reassemblyBuffer ! = null & & ( value [ 2 ] = = 0x40 | | value [ 2 ] = = ( byte ) 0x80 ) ) {
2022-01-21 12:48:36 +01:00
byte [ ] payload = new byte [ value . length - 4 ] ;
System . arraycopy ( value , 4 , payload , 0 , payload . length ) ;
2022-05-15 18:52:01 +02:00
reassemblyBuffer = ArrayUtils . addAll ( reassemblyBuffer , payload ) ;
2022-01-21 12:48:36 +01:00
if ( value [ 2 ] = = ( byte ) 0x80 ) {
done = true ;
}
}
if ( ! done ) {
2022-08-18 23:03:28 +02:00
LOG . info ( " got chunk of configuration data for {} " , String . format ( " 0x%02x " , reassemblyType ) ) ;
2022-01-21 12:48:36 +01:00
} else {
LOG . info ( " got full/reassembled configuration data " ) ;
2022-05-15 18:52:01 +02:00
switch ( reassemblyType ) {
case COMMAND_ALARMS_WITH_TIMES :
decodeAndUpdateAlarmStatus ( reassemblyBuffer , true ) ;
break ;
case COMMAND_WORKOUT_ACTIVITY_TYPES :
LOG . warn ( " got workout activity types, not handled " ) ;
2022-09-25 11:10:38 +02:00
logMessageContent ( reassemblyBuffer ) ;
2022-05-15 18:52:01 +02:00
break ;
default :
2022-08-18 23:03:28 +02:00
LOG . warn ( " got unknown chunked configuration response for {}, not handled " , String . format ( " 0x%02x " , reassemblyType ) ) ;
2022-05-15 18:52:01 +02:00
break ;
}
reassemblyBuffer = null ;
2022-01-21 12:48:36 +01:00
}
2019-12-27 22:19:17 +01:00
} else {
2022-01-20 10:09:23 +01:00
LOG . warn ( " unknown response got from configuration request " + GB . hexdump ( value , 0 , - 1 ) ) ;
2019-12-27 22:19:17 +01:00
}
}
2023-06-11 16:45:41 +02:00
private void handleChunked ( final byte [ ] value ) {
switch ( value [ 0 ] ) {
case 0x03 :
if ( huami2021ChunkedDecoder ! = null ) {
final boolean needsAck = huami2021ChunkedDecoder . decode ( value ) ;
if ( needsAck ) {
sendChunkedAck ( ) ;
}
} else {
LOG . warn ( " Got chunked payload, but decoder is null " ) ;
}
return ;
case 0x04 :
final byte handle = value [ 2 ] ;
final byte count = value [ 4 ] ;
LOG . info ( " Got chunked ack, handle={}, count={} " , handle , count ) ;
// TODO: We should probably update the handle and count on the encoder
return ;
default :
LOG . warn ( " Unhandled chunked payload of type {} " , value [ 0 ] ) ;
}
}
public void sendChunkedAck ( ) {
if ( characteristicChunked2021Read = = null ) {
LOG . error ( " Chunked read characteristic is null, can't send ack " ) ;
return ;
}
final byte handle = huami2021ChunkedDecoder . getLastHandle ( ) ;
final byte count = huami2021ChunkedDecoder . getLastCount ( ) ;
try {
final TransactionBuilder builder = performInitialized ( " send chunked ack " ) ;
builder . write ( characteristicChunked2021Read , new byte [ ] { 0x04 , 0x00 , handle , 0x01 , count } ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( final Exception e ) {
LOG . error ( " Failed to send chunked ack " , e ) ;
}
}
2022-01-20 10:09:23 +01:00
private void decodeAndUpdateAlarmStatus ( byte [ ] response , boolean withTimes ) {
2019-12-27 22:19:17 +01:00
List < nodomain . freeyourgadget . gadgetbridge . entities . Alarm > alarms = DBHelper . getAlarms ( gbDevice ) ;
2020-02-26 22:25:03 +01:00
int maxAlarms = 10 ;
2022-01-20 10:09:23 +01:00
//FIXME: we can rather have a full struct here probably
2020-02-26 22:25:03 +01:00
boolean [ ] alarmsInUse = new boolean [ maxAlarms ] ;
boolean [ ] alarmsEnabled = new boolean [ maxAlarms ] ;
2022-01-20 10:09:23 +01:00
byte [ ] alarmsMinute = new byte [ maxAlarms ] ;
byte [ ] alarmsHour = new byte [ maxAlarms ] ;
2022-01-20 11:12:31 +01:00
byte [ ] alarmsRepetition = new byte [ maxAlarms ] ;
2022-01-20 10:09:23 +01:00
int nr_alarms ;
byte enable_flag ;
if ( withTimes ) {
2022-01-21 12:48:36 +01:00
nr_alarms = ( response . length - 1 ) / 4 ;
2022-01-20 10:09:23 +01:00
enable_flag = ( byte ) 0x80 ;
} else {
nr_alarms = response [ 8 ] ;
enable_flag = ( byte ) 0x10 ;
}
2019-12-27 22:19:17 +01:00
for ( int i = 0 ; i < nr_alarms ; i + + ) {
2022-01-20 10:09:23 +01:00
int offset ;
if ( withTimes ) {
2022-01-21 12:48:36 +01:00
offset = i * 4 + 1 ;
2022-01-20 10:09:23 +01:00
} else {
offset = 9 + i ;
}
byte alarm_data = response [ offset ] ;
2019-12-27 22:19:17 +01:00
int index = alarm_data & 0xf ;
2020-02-26 22:25:03 +01:00
if ( index > = maxAlarms ) {
GB . toast ( " Unexpected alarm index from device, ignoring: " + index , Toast . LENGTH_SHORT , GB . ERROR ) ;
return ;
}
2019-12-27 22:19:17 +01:00
alarmsInUse [ index ] = true ;
2022-01-20 10:09:23 +01:00
boolean enabled = ( alarm_data & enable_flag ) = = enable_flag ;
2019-12-27 22:19:17 +01:00
alarmsEnabled [ index ] = enabled ;
2022-01-20 11:12:31 +01:00
if ( withTimes ) {
alarmsHour [ index ] = response [ offset + 1 ] ;
alarmsMinute [ index ] = response [ offset + 2 ] ;
alarmsRepetition [ index ] = response [ offset + 3 ] ;
}
2019-12-27 22:19:17 +01:00
LOG . info ( " alarm " + index + " is enabled: " + enabled ) ;
}
for ( nodomain . freeyourgadget . gadgetbridge . entities . Alarm alarm : alarms ) {
2022-01-20 10:09:23 +01:00
int pos = alarm . getPosition ( ) ;
boolean enabled = alarmsEnabled [ pos ] ;
boolean unused = ! alarmsInUse [ pos ] ;
2022-01-20 11:12:31 +01:00
if ( alarm . getEnabled ( ) ! = enabled | | alarm . getUnused ( ) ! = unused | | ( withTimes & & ! unused & & ( alarm . getHour ( ) ! = alarmsHour [ pos ] | | alarm . getMinute ( ) ! = alarmsMinute [ pos ] | | alarm . getRepetition ( ) ! = alarmsRepetition [ pos ] ) ) ) {
2022-01-20 10:09:23 +01:00
LOG . info ( " updating alarm index " + pos + " unused= " + unused + " , enabled= " + enabled ) ;
2019-12-27 22:19:17 +01:00
alarm . setEnabled ( enabled ) ;
alarm . setUnused ( unused ) ;
2022-01-20 11:12:31 +01:00
if ( withTimes & & ! unused ) {
2022-01-20 10:09:23 +01:00
alarm . setHour ( alarmsHour [ pos ] ) ;
alarm . setMinute ( alarmsMinute [ pos ] ) ;
2022-01-20 11:12:31 +01:00
alarm . setRepetition ( alarmsRepetition [ pos ] ) ;
2022-01-20 10:09:23 +01:00
}
2019-12-27 22:19:17 +01:00
DBHelper . store ( alarm ) ;
2019-12-29 11:07:42 +01:00
Intent intent = new Intent ( DeviceService . ACTION_SAVE_ALARMS ) ;
LocalBroadcastManager . getInstance ( getContext ( ) ) . sendBroadcast ( intent ) ;
2019-12-27 22:19:17 +01:00
}
}
}
2022-08-18 23:03:28 +02:00
protected void enableRealtimeSamplesTimer ( boolean enable ) {
2016-11-29 23:22:36 +01:00
if ( enable ) {
getRealtimeSamplesSupport ( ) . start ( ) ;
} else {
if ( realtimeSamplesSupport ! = null ) {
realtimeSamplesSupport . stop ( ) ;
}
}
}
2022-08-18 23:03:28 +02:00
private MiBandActivitySample createActivitySample ( Device device , User user , int timestampInSeconds , SampleProvider provider ) {
2016-11-29 23:22:36 +01:00
MiBandActivitySample sample = new MiBandActivitySample ( ) ;
sample . setDevice ( device ) ;
sample . setUser ( user ) ;
sample . setTimestamp ( timestampInSeconds ) ;
sample . setProvider ( provider ) ;
return sample ;
}
private RealtimeSamplesSupport getRealtimeSamplesSupport ( ) {
if ( realtimeSamplesSupport = = null ) {
realtimeSamplesSupport = new RealtimeSamplesSupport ( 1000 , 1000 ) {
@Override
public void doCurrentSample ( ) {
try ( DBHandler handler = GBApplication . acquireDB ( ) ) {
DaoSession session = handler . getDaoSession ( ) ;
2019-05-20 16:36:06 +02:00
Device device = DBHelper . getDevice ( gbDevice , session ) ;
2016-11-29 23:22:36 +01:00
User user = DBHelper . getUser ( session ) ;
int ts = ( int ) ( System . currentTimeMillis ( ) / 1000 ) ;
MiBand2SampleProvider provider = new MiBand2SampleProvider ( gbDevice , session ) ;
MiBandActivitySample sample = createActivitySample ( device , user , ts , provider ) ;
sample . setHeartRate ( getHeartrateBpm ( ) ) ;
2018-09-11 23:26:51 +02:00
// sample.setSteps(getSteps());
2016-11-29 23:22:36 +01:00
sample . setRawIntensity ( ActivitySample . NOT_MEASURED ) ;
2018-06-14 16:30:43 +02:00
sample . setRawKind ( HuamiConst . TYPE_ACTIVITY ) ; // to make it visible in the charts TODO: add a MANUAL kind for that?
2016-11-29 23:22:36 +01:00
2016-12-26 11:33:01 +01:00
provider . addGBActivitySample ( sample ) ;
// set the steps only afterwards, since realtime steps are also recorded
// in the regular samples and we must not count them twice
// Note: we know that the DAO sample is never committed again, so we simply
// change the value here in memory.
sample . setSteps ( getSteps ( ) ) ;
if ( LOG . isDebugEnabled ( ) ) {
LOG . debug ( " realtime sample: " + sample ) ;
}
2016-12-26 00:23:02 +01:00
Intent intent = new Intent ( DeviceService . ACTION_REALTIME_SAMPLES )
. putExtra ( DeviceService . EXTRA_REALTIME_SAMPLE , sample ) ;
LocalBroadcastManager . getInstance ( getContext ( ) ) . sendBroadcast ( intent ) ;
2016-11-29 23:22:36 +01:00
} catch ( Exception e ) {
LOG . warn ( " Unable to acquire db for saving realtime samples " , e ) ;
}
}
} ;
}
return realtimeSamplesSupport ;
2016-07-25 00:00:22 +02:00
}
private void handleDeviceName ( byte [ ] value , int status ) {
// if (status == BluetoothGatt.GATT_SUCCESS) {
// versionCmd.hwVersion = new String(value);
// handleGBDeviceEvent(versionCmd);
// }
}
/ * *
* Convert an alarm from the GB internal structure to a Mi Band message and put on the specified
* builder queue as a write message for the passed characteristic
*
* @param alarm
* @param builder
* /
2022-08-18 23:03:28 +02:00
protected void queueAlarm ( Alarm alarm , TransactionBuilder builder ) {
2018-11-24 12:16:47 +01:00
DeviceCoordinator coordinator = DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
2022-08-18 23:03:28 +02:00
Calendar calendar = AlarmUtils . toCalendar ( alarm ) ;
2016-10-21 01:01:30 +02:00
2020-01-12 08:12:21 +01:00
int actionMask = 0 ;
2019-12-24 01:27:57 +01:00
int daysMask = 0 ;
if ( alarm . getEnabled ( ) & & ! alarm . getUnused ( ) ) {
2020-01-12 08:12:21 +01:00
actionMask = 0x80 ;
if ( coordinator . supportsAlarmSnoozing ( ) & & ! alarm . getSnooze ( ) ) {
actionMask | = 0x40 ;
}
2016-10-28 23:18:10 +02:00
}
2019-12-24 01:27:57 +01:00
if ( ! alarm . getUnused ( ) ) {
daysMask = alarm . getRepetition ( ) ;
if ( ! alarm . isRepetitive ( ) ) {
daysMask = 128 ;
}
2016-10-28 23:18:10 +02:00
}
2019-12-24 01:27:57 +01:00
2021-09-01 00:16:50 +02:00
byte [ ] alarmMessage = new byte [ ] {
2016-10-23 23:05:54 +02:00
( byte ) 0x2 , // TODO what is this?
2020-01-12 08:12:21 +01:00
( byte ) ( actionMask | alarm . getPosition ( ) ) , // action mask + alarm slot
2016-10-21 00:49:42 +02:00
( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ,
( byte ) calendar . get ( Calendar . MINUTE ) ,
( byte ) daysMask ,
2016-07-25 00:00:22 +02:00
} ;
2021-09-01 00:16:50 +02:00
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , alarmMessage ) ;
2021-09-01 00:16:50 +02:00
2016-10-23 22:37:18 +02:00
// TODO: react on 0x10, 0x02, 0x01 on notification (success)
2016-07-25 00:00:22 +02:00
}
2020-07-22 11:03:30 +02:00
protected void handleDeviceInfo ( nodomain . freeyourgadget . gadgetbridge . service . btle . profiles . deviceinfo . DeviceInfo info ) {
2016-07-25 00:00:22 +02:00
// if (getDeviceInfo().supportsHeartrate()) {
// getDevice().addDeviceInfo(new GenericItem(
// getContext().getString(R.string.DEVINFO_HR_VER),
// info.getSoftwareRevision()));
// }
2017-09-10 21:11:50 +02:00
2016-07-25 00:00:22 +02:00
LOG . warn ( " Device info: " + info ) ;
versionCmd . hwVersion = info . getHardwareRevision ( ) ;
2018-08-06 20:26:39 +02:00
versionCmd . fwVersion = info . getFirmwareRevision ( ) ;
if ( versionCmd . fwVersion = = null ) {
versionCmd . fwVersion = info . getSoftwareRevision ( ) ;
}
2017-03-15 00:26:39 +01:00
if ( versionCmd . fwVersion ! = null & & versionCmd . fwVersion . length ( ) > 0 & & versionCmd . fwVersion . charAt ( 0 ) = = 'V' ) {
versionCmd . fwVersion = versionCmd . fwVersion . substring ( 1 ) ;
}
2016-07-25 00:00:22 +02:00
handleGBDeviceEvent ( versionCmd ) ;
}
private void handleBatteryInfo ( byte [ ] value , int status ) {
if ( status = = BluetoothGatt . GATT_SUCCESS ) {
2018-06-14 18:16:49 +02:00
HuamiBatteryInfo info = new HuamiBatteryInfo ( value ) ;
2022-08-18 23:03:28 +02:00
handleGBDeviceEvent ( info . toDeviceEvent ( ) ) ;
2016-07-25 00:00:22 +02:00
}
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport sendCalendarEvents ( TransactionBuilder builder ) {
if ( characteristicChunked = = null ) { // all except Mi Band 2
sendCalendarEventsAsAlarms ( builder ) ;
} else {
sendCalendarEventsAsReminders ( builder ) ;
}
return this ;
}
2016-07-25 00:00:22 +02:00
/ * *
* Fetch the events from the android device calendars and set the alarms on the miband .
2016-12-01 22:49:58 +01:00
* @param builder
2016-07-25 00:00:22 +02:00
* /
2022-08-18 23:03:28 +02:00
private HuamiSupport sendCalendarEventsAsAlarms ( TransactionBuilder builder ) {
DeviceCoordinator coordinator = DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
2019-12-14 23:43:54 +01:00
Prefs prefs = new Prefs ( GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ) ;
2023-06-13 23:02:26 +02:00
int maxAlarms = coordinator . getAlarmSlotCount ( gbDevice ) ;
2022-08-18 23:03:28 +02:00
int availableSlots = Math . min ( prefs . getInt ( PREF_RESERVER_ALARMS_CALENDAR , 0 ) , maxAlarms ) ;
if ( availableSlots < = 0 ) {
return this ;
}
2016-07-25 00:00:22 +02:00
2022-08-18 23:03:28 +02:00
CalendarManager upcomingEvents = new CalendarManager ( getContext ( ) , getDevice ( ) . getAddress ( ) ) ;
List < CalendarEvent > mEvents = upcomingEvents . getCalendarEventList ( ) ;
2016-07-25 00:00:22 +02:00
2022-08-18 23:03:28 +02:00
int iteration = 0 ;
2018-11-24 12:16:47 +01:00
2022-08-18 23:03:28 +02:00
for ( CalendarEvent mEvt : mEvents ) {
if ( mEvt . isAllDay ( ) ) {
continue ;
2016-07-25 00:00:22 +02:00
}
2022-08-18 23:03:28 +02:00
if ( iteration > = availableSlots ) {
break ;
}
int slotToUse = 2 - iteration ;
Calendar calendar = Calendar . getInstance ( ) ;
calendar . setTimeInMillis ( mEvt . getBegin ( ) ) ;
Alarm alarm = AlarmUtils . createSingleShot ( slotToUse , false , true , calendar ) ;
queueAlarm ( alarm , builder ) ;
iteration + + ;
2016-07-25 00:00:22 +02:00
}
2022-08-18 23:03:28 +02:00
2016-12-01 22:49:58 +01:00
return this ;
2016-07-25 00:00:22 +02:00
}
2022-08-18 23:03:28 +02:00
private HuamiSupport sendCalendarEventsAsReminders ( TransactionBuilder builder ) {
2020-02-24 14:19:06 +01:00
boolean syncCalendar = GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) . getBoolean ( PREF_SYNC_CALENDAR , false ) ;
if ( ! syncCalendar ) {
return this ;
}
2022-08-18 23:03:28 +02:00
final DeviceCoordinator coordinator = DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
2020-02-24 14:19:06 +01:00
2021-12-04 16:55:09 +01:00
final Prefs prefs = new Prefs ( GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ) ;
2022-08-18 23:03:28 +02:00
int availableSlots = prefs . getInt ( PREF_RESERVER_REMINDERS_CALENDAR , coordinator . supportsCalendarEvents ( ) ? 0 : 9 ) ;
2021-12-04 16:55:09 +01:00
2022-06-16 23:28:17 +02:00
CalendarManager upcomingEvents = new CalendarManager ( getContext ( ) , getDevice ( ) . getAddress ( ) ) ;
List < CalendarEvent > calendarEvents = upcomingEvents . getCalendarEventList ( ) ;
2020-02-18 22:20:57 +01:00
Calendar calendar = Calendar . getInstance ( ) ;
int iteration = 0 ;
2022-06-16 23:28:17 +02:00
for ( CalendarEvent calendarEvent : calendarEvents ) {
2020-03-20 17:21:20 +01:00
if ( calendarEvent . isAllDay ( ) ) {
continue ;
}
2021-12-04 16:55:09 +01:00
if ( iteration > = availableSlots ) {
2020-02-18 22:20:57 +01:00
break ;
}
2020-03-04 23:13:57 +01:00
2020-02-18 22:20:57 +01:00
calendar . setTimeInMillis ( calendarEvent . getBegin ( ) ) ;
2020-03-04 23:13:57 +01:00
byte [ ] title ;
if ( calendarEvent . getTitle ( ) ! = null ) {
title = calendarEvent . getTitle ( ) . getBytes ( ) ;
} else {
title = new byte [ ] { } ;
}
2020-02-18 22:20:57 +01:00
2021-03-06 20:44:13 +01:00
int length = 1 + 1 + 4 + 6 + 6 + 1 + title . length + 1 ;
2020-02-18 22:20:57 +01:00
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 0x0b ) ; // always 0x0b?
2021-03-06 20:44:13 +01:00
buf . put ( ( byte ) iteration ) ; // id
2020-02-18 22:20:57 +01:00
buf . putInt ( 0x08 | 0x04 | 0x01 ) ; // flags 0x01 = enable, 0x04 = end date present, 0x08 = has text
calendar . setTimeInMillis ( calendarEvent . getBegin ( ) ) ;
buf . put ( BLETypeConversions . shortCalendarToRawBytes ( calendar ) ) ;
calendar . setTimeInMillis ( calendarEvent . getEnd ( ) ) ;
buf . put ( BLETypeConversions . shortCalendarToRawBytes ( calendar ) ) ;
buf . put ( ( byte ) 0 ) ; // 0 Terminated
2021-03-06 20:44:13 +01:00
buf . put ( title ) ;
2020-02-18 22:20:57 +01:00
buf . put ( ( byte ) 0 ) ; // 0 Terminated
writeToChunked ( builder , 2 , buf . array ( ) ) ;
iteration + + ;
}
2021-03-06 21:52:06 +01:00
// Continue by deleting the events
2021-12-04 16:55:09 +01:00
for ( ; iteration < availableSlots ; iteration + + ) {
2021-03-06 21:52:06 +01:00
int length = 1 + 1 + 4 + 6 + 6 + 1 + 0 + 1 ;
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 0x0b ) ; // always 0x0b?
buf . put ( ( byte ) iteration ) ; // id
buf . putInt ( 0x08 ) ; // flags 0x01 = enable, 0x04 = end date present, 0x08 = has text
buf . put ( new byte [ 6 + 6 + 1 + 1 ] ) ; // default value is 0
writeToChunked ( builder , 2 , buf . array ( ) ) ;
}
2020-02-18 22:20:57 +01:00
return this ;
}
2016-11-13 20:47:24 +01:00
@Override
public void onSendConfiguration ( String config ) {
2017-03-03 22:32:54 +01:00
TransactionBuilder builder ;
2016-11-13 20:47:24 +01:00
try {
builder = performInitialized ( " Sending configuration for option: " + config ) ;
switch ( config ) {
case MiBandConst . PREF_MI2_DATEFORMAT :
setDateDisplay ( builder ) ;
break ;
2022-05-14 22:08:32 +02:00
case PREF_USER_FITNESS_GOAL_NOTIFICATION :
2017-07-15 00:40:58 +02:00
setGoalNotification ( builder ) ;
break ;
2022-03-07 17:42:46 +01:00
case PREF_ACTIVATE_DISPLAY_ON_LIFT :
case PREF_DISPLAY_ON_LIFT_START :
case PREF_DISPLAY_ON_LIFT_END :
2016-11-13 21:33:43 +01:00
setActivateDisplayOnLiftWrist ( builder ) ;
break ;
2022-05-09 14:57:44 +02:00
case PREF_DISPLAY_ON_LIFT_SENSITIVITY :
setActivateDisplayOnLiftWristSensitivity ( builder ) ;
break ;
2022-03-07 17:52:50 +01:00
case PREF_DISCONNECT_NOTIFICATION :
case PREF_DISCONNECT_NOTIFICATION_START :
case PREF_DISCONNECT_NOTIFICATION_END :
2019-02-13 13:06:42 +01:00
setDisconnectNotification ( builder ) ;
break ;
2019-05-20 16:36:06 +02:00
case HuamiConst . PREF_DISPLAY_ITEMS :
2020-11-06 21:47:54 +01:00
case HuamiConst . PREF_DISPLAY_ITEMS_SORTABLE :
2017-07-09 16:17:13 +02:00
setDisplayItems ( builder ) ;
break ;
2020-05-13 10:15:12 +02:00
case HuamiConst . PREF_SHORTCUTS :
2020-11-06 21:47:54 +01:00
case HuamiConst . PREF_SHORTCUTS_SORTABLE :
2020-05-13 10:15:12 +02:00
setShortcuts ( builder ) ;
break ;
2022-05-09 15:05:08 +02:00
case HuamiConst . PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE :
setWorkoutActivityTypes ( builder ) ;
break ;
2017-07-09 14:30:03 +02:00
case MiBandConst . PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO :
setRotateWristToSwitchInfo ( builder ) ;
break ;
2017-03-11 11:29:50 +01:00
case ActivityUser . PREF_USER_STEPS_GOAL :
2022-10-22 21:53:45 +02:00
case ActivityUser . PREF_USER_CALORIES_BURNT :
case ActivityUser . PREF_USER_SLEEP_DURATION :
case ActivityUser . PREF_USER_GOAL_WEIGHT_KG :
case ActivityUser . PREF_USER_GOAL_STANDING_TIME_HOURS :
case ActivityUser . PREF_USER_GOAL_FAT_BURN_TIME_MINUTES :
2016-11-24 21:58:32 +01:00
setFitnessGoal ( builder ) ;
break ;
2022-08-18 23:03:28 +02:00
case MiBandConst . PREF_NIGHT_MODE :
case MiBandConst . PREF_NIGHT_MODE_START :
case MiBandConst . PREF_NIGHT_MODE_END :
setNightMode ( builder ) ;
break ;
2022-03-06 23:48:47 +01:00
case PREF_DO_NOT_DISTURB :
case PREF_DO_NOT_DISTURB_START :
case PREF_DO_NOT_DISTURB_END :
case PREF_DO_NOT_DISTURB_LIFT_WRIST :
2017-07-15 15:01:07 +02:00
setDoNotDisturb ( builder ) ;
2017-07-15 22:48:26 +02:00
break ;
2022-03-07 15:51:54 +01:00
case PREF_INACTIVITY_ENABLE :
case PREF_INACTIVITY_THRESHOLD :
case PREF_INACTIVITY_START :
case PREF_INACTIVITY_END :
case PREF_INACTIVITY_DND :
case PREF_INACTIVITY_DND_START :
case PREF_INACTIVITY_DND_END :
2017-07-15 22:48:26 +02:00
setInactivityWarnings ( builder ) ;
break ;
2022-08-09 14:30:21 +02:00
case PREF_HOURLY_CHIME_ENABLE :
case PREF_HOURLY_CHIME_START :
case PREF_HOURLY_CHIME_END :
setHourlyChime ( builder ) ;
break ;
2017-10-02 22:23:17 +02:00
case SettingsActivity . PREF_MEASUREMENT_SYSTEM :
setDistanceUnit ( builder ) ;
break ;
2019-05-22 00:42:22 +02:00
case MiBandConst . PREF_SWIPE_UNLOCK :
setBandScreenUnlock ( builder ) ;
break ;
2020-02-04 10:04:01 +01:00
case PREF_TIMEFORMAT :
2020-01-04 23:40:50 +01:00
setTimeFormat ( builder ) ;
break ;
2020-02-04 10:04:01 +01:00
case PREF_DATEFORMAT :
2019-07-16 23:48:08 +02:00
setDateFormat ( builder ) ;
break ;
2020-05-05 00:34:59 +02:00
case PREF_LANGUAGE :
2019-08-02 00:11:11 +02:00
setLanguage ( builder ) ;
break ;
2019-08-27 11:13:45 +02:00
case HuamiConst . PREF_EXPOSE_HR_THIRDPARTY :
2022-08-18 23:03:28 +02:00
setExposeHRThirdParty ( builder ) ;
2019-08-27 11:13:45 +02:00
break ;
2020-12-06 00:09:19 +01:00
case PREF_BT_CONNECTED_ADVERTISEMENT :
setBtConnectedAdvertising ( builder ) ;
break ;
2020-02-04 10:04:01 +01:00
case PREF_WEARLOCATION :
2019-10-05 22:35:30 +02:00
setWearLocation ( builder ) ;
break ;
2021-03-24 20:02:48 +01:00
case PREF_SOUNDS :
setBeepSounds ( builder ) ;
break ;
2021-05-27 23:11:00 +02:00
case PREF_USER_NAME :
case PREF_USER_YEAR_OF_BIRTH :
case PREF_USER_WEIGHT_KG :
case PREF_USER_HEIGHT_CM :
case PREF_USER_GENDER :
setUserInfo ( builder ) ;
break ;
2022-05-14 15:20:28 +02:00
case PREF_HUAMI_VIBRATION_PROFILE_APP_ALERTS :
case PREF_HUAMI_VIBRATION_PROFILE_INCOMING_CALL :
case PREF_HUAMI_VIBRATION_PROFILE_INCOMING_SMS :
case PREF_HUAMI_VIBRATION_PROFILE_GOAL_NOTIFICATION :
case PREF_HUAMI_VIBRATION_PROFILE_ALARM :
case PREF_HUAMI_VIBRATION_PROFILE_IDLE_ALERTS :
case PREF_HUAMI_VIBRATION_PROFILE_EVENT_REMINDER :
case PREF_HUAMI_VIBRATION_PROFILE_FIND_BAND :
2022-10-22 21:53:45 +02:00
case PREF_HUAMI_VIBRATION_PROFILE_TODO_LIST :
case PREF_HUAMI_VIBRATION_PROFILE_SCHEDULE :
2022-05-14 15:20:28 +02:00
case PREF_HUAMI_VIBRATION_COUNT_APP_ALERTS :
case PREF_HUAMI_VIBRATION_COUNT_INCOMING_CALL :
case PREF_HUAMI_VIBRATION_COUNT_INCOMING_SMS :
case PREF_HUAMI_VIBRATION_COUNT_GOAL_NOTIFICATION :
case PREF_HUAMI_VIBRATION_COUNT_ALARM :
case PREF_HUAMI_VIBRATION_COUNT_IDLE_ALERTS :
case PREF_HUAMI_VIBRATION_COUNT_EVENT_REMINDER :
case PREF_HUAMI_VIBRATION_COUNT_FIND_BAND :
2022-10-22 21:53:45 +02:00
case PREF_HUAMI_VIBRATION_COUNT_TODO_LIST :
case PREF_HUAMI_VIBRATION_COUNT_SCHEDULE :
2022-05-14 15:20:28 +02:00
case PREF_HUAMI_VIBRATION_TRY_APP_ALERTS :
case PREF_HUAMI_VIBRATION_TRY_INCOMING_CALL :
case PREF_HUAMI_VIBRATION_TRY_INCOMING_SMS :
case PREF_HUAMI_VIBRATION_TRY_GOAL_NOTIFICATION :
case PREF_HUAMI_VIBRATION_TRY_ALARM :
case PREF_HUAMI_VIBRATION_TRY_IDLE_ALERTS :
case PREF_HUAMI_VIBRATION_TRY_EVENT_REMINDER :
case PREF_HUAMI_VIBRATION_TRY_FIND_BAND :
2022-10-22 21:53:45 +02:00
case PREF_HUAMI_VIBRATION_TRY_TODO_LIST :
case PREF_HUAMI_VIBRATION_TRY_SCHEDULE :
2022-05-14 15:20:28 +02:00
setVibrationPattern ( builder , config ) ;
break ;
2022-05-16 18:42:02 +02:00
case PREF_HEARTRATE_ACTIVITY_MONITORING :
setHeartrateActivityMonitoring ( builder ) ;
break ;
case PREF_HEARTRATE_ALERT_ENABLED :
2022-08-18 23:03:28 +02:00
case PREF_HEARTRATE_ALERT_HIGH_THRESHOLD :
case PREF_HEARTRATE_ALERT_LOW_THRESHOLD :
2022-05-16 18:42:02 +02:00
setHeartrateAlert ( builder ) ;
break ;
case PREF_HEARTRATE_STRESS_MONITORING :
setHeartrateStressMonitoring ( builder ) ;
break ;
2022-07-05 20:29:16 +02:00
case PasswordCapabilityImpl . PREF_PASSWORD :
case PasswordCapabilityImpl . PREF_PASSWORD_ENABLED :
setPassword ( builder ) ;
break ;
2016-11-13 20:47:24 +01:00
}
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
GB . toast ( " Error setting configuration " , Toast . LENGTH_LONG , GB . ERROR , e ) ;
}
}
2016-10-11 23:06:59 +02:00
@Override
public void onTestNewFunction ( ) {
2022-01-21 12:48:36 +01:00
//requestMTU(23);
2021-02-07 16:23:45 +01:00
try {
2022-05-15 18:52:01 +02:00
final TransactionBuilder builder = performInitialized ( " test request " ) ;
writeToConfiguration ( builder , HuamiService . COMMAND_REQUEST_WORKOUT_ACTIVITY_TYPES ) ;
2021-02-07 16:23:45 +01:00
builder . queue ( getQueue ( ) ) ;
2022-05-14 15:20:28 +02:00
} catch ( final Exception e ) {
LOG . error ( " onTestNewFunction failed " , e ) ;
}
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setVibrationPattern ( final TransactionBuilder builder , final String preferenceKey ) {
2022-05-14 15:20:28 +02:00
// The preference key has one of the 3 prefixes
final String notificationTypeName = preferenceKey . replace ( PREF_HUAMI_VIBRATION_COUNT_PREFIX , " " )
. replace ( PREF_HUAMI_VIBRATION_PROFILE_PREFIX , " " )
. replace ( PREF_HUAMI_VIBRATION_TRY_PREFIX , " " )
. toUpperCase ( Locale . ROOT ) ;
final HuamiVibrationPatternNotificationType notificationType = HuamiVibrationPatternNotificationType . valueOf ( notificationTypeName ) ;
final boolean isTry = preferenceKey . startsWith ( PREF_HUAMI_VIBRATION_TRY_PREFIX ) ;
2022-10-22 21:53:45 +02:00
final VibrationProfile vibrationProfile = HuamiCoordinator . getVibrationProfile (
getDevice ( ) . getAddress ( ) ,
notificationType ,
supportsDeviceDefaultVibrationProfiles ( )
) ;
2022-05-14 15:20:28 +02:00
setVibrationPattern ( builder , notificationType , isTry , vibrationProfile ) ;
2022-08-18 23:03:28 +02:00
return this ;
2022-05-14 15:20:28 +02:00
}
2022-10-22 21:53:45 +02:00
/ * *
* Whether the device supports built - in default vibration profiles .
* /
protected boolean supportsDeviceDefaultVibrationProfiles ( ) {
return false ;
}
2022-05-14 15:20:28 +02:00
/ * *
* Test or set a { @link VibrationProfile } .
*
* @param builder the { @link TransactionBuilder }
* @param notificationType the notification type
* @param test test the pattern ( only vibrate the band , do not set it )
* @param profile the { @link VibrationProfile }
* /
2022-08-18 23:03:28 +02:00
protected void setVibrationPattern ( final TransactionBuilder builder ,
2022-10-22 21:53:45 +02:00
final HuamiVibrationPatternNotificationType notificationType ,
final boolean test ,
final VibrationProfile profile ) {
if ( profile = = null ) {
LOG . error ( " Vibration profile is null for {} " , notificationType ) ;
return ;
}
2022-05-14 15:20:28 +02:00
final int MAX_TOTAL_LENGTH_MS = 10_000 ; // 10 seconds, about as long as Mi Fit allows
// The on-off sequence, until the max total length is reached
2022-08-18 23:03:28 +02:00
final List < Short > onOff = truncateVibrationsOnOff ( profile , MAX_TOTAL_LENGTH_MS ) ;
2022-05-14 15:20:28 +02:00
final ByteBuffer buf = ByteBuffer . allocate ( 3 + 2 * onOff . size ( ) ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 0x20 ) ;
buf . put ( notificationType . getCode ( ) ) ;
byte flag = ( byte ) ( onOff . size ( ) / 2 ) ;
flag | = 0x40 ;
if ( test ) {
flag | = 0x80 ;
}
buf . put ( flag ) ;
for ( Short time : onOff ) {
buf . putShort ( time ) ;
}
writeToChunked ( builder , 2 , buf . array ( ) ) ;
2016-10-11 23:06:59 +02:00
}
2016-11-13 01:42:55 +01:00
2022-08-18 23:03:28 +02:00
protected List < Short > truncateVibrationsOnOff ( final VibrationProfile profile , final int limitMillis ) {
2022-10-22 21:53:45 +02:00
if ( profile = = null ) {
return Collections . emptyList ( ) ;
}
2022-08-18 23:03:28 +02:00
int totalLengthMs = 0 ;
// The on-off sequence, until the max total length is reached
final List < Short > onOff = new ArrayList < > ( profile . getOnOffSequence ( ) . length ) ;
for ( int c = 0 ; c < profile . getRepeat ( ) ; c + + ) {
for ( int i = 0 ; i < profile . getOnOffSequence ( ) . length ; i + = 2 ) {
final short on = ( short ) profile . getOnOffSequence ( ) [ i ] ;
final short off = ( short ) profile . getOnOffSequence ( ) [ i + 1 ] ;
if ( totalLengthMs + on + off > limitMillis ) {
LOG . warn ( " VibrationProfile {} too long, truncating to {} ms " , profile . getId ( ) , limitMillis ) ;
break ;
}
onOff . add ( on ) ;
onOff . add ( off ) ;
totalLengthMs + = on + off ;
}
}
return onOff ;
}
2016-12-31 15:56:05 +01:00
@Override
public void onSendWeather ( WeatherSpec weatherSpec ) {
2022-06-14 20:15:51 +02:00
final DeviceCoordinator coordinator = DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
if ( ! coordinator . supportsWeather ( ) ) {
2019-08-25 09:55:23 +02:00
return ;
}
if ( gbDevice . getFirmwareVersion ( ) = = null ) {
LOG . warn ( " Device not initialized yet, so not sending weather info " ) ;
return ;
}
2019-10-31 15:30:05 +01:00
boolean supportsConditionString = true ;
2019-08-25 09:55:23 +02:00
Version version = new Version ( gbDevice . getFirmwareVersion ( ) ) ;
2019-10-31 15:30:05 +01:00
if ( gbDevice . getType ( ) = = DeviceType . AMAZFITBIP & & version . compareTo ( new Version ( " 0.0.8.74 " ) ) < 0 ) {
supportsConditionString = false ;
2019-08-25 09:55:23 +02:00
}
MiBandConst . DistanceUnit unit = HuamiCoordinator . getDistanceUnit ( ) ;
int tz_offset_hours = SimpleTimeZone . getDefault ( ) . getOffset ( weatherSpec . timestamp * 1000L ) / ( 1000 * 60 * 60 ) ;
try {
TransactionBuilder builder ;
builder = performInitialized ( " Sending current temp " ) ;
byte condition = HuamiWeatherConditions . mapToAmazfitBipWeatherCode ( weatherSpec . currentConditionCode ) ;
int length = 8 ;
if ( supportsConditionString ) {
length + = weatherSpec . currentCondition . getBytes ( ) . length + 1 ;
}
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 2 ) ;
buf . putInt ( weatherSpec . timestamp ) ;
buf . put ( ( byte ) ( tz_offset_hours * 4 ) ) ;
buf . put ( condition ) ;
int currentTemp = weatherSpec . currentTemp - 273 ;
if ( unit = = MiBandConst . DistanceUnit . IMPERIAL ) {
currentTemp = ( int ) WeatherUtils . celsiusToFahrenheit ( currentTemp ) ;
}
buf . put ( ( byte ) currentTemp ) ;
if ( supportsConditionString ) {
buf . put ( weatherSpec . currentCondition . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
}
if ( characteristicChunked ! = null ) {
writeToChunked ( builder , 1 , buf . array ( ) ) ;
} else {
builder . write ( getCharacteristic ( AmazfitBipService . UUID_CHARACTERISTIC_WEATHER ) , buf . array ( ) ) ;
}
builder . queue ( getQueue ( ) ) ;
} catch ( Exception ex ) {
LOG . error ( " Error sending current weather " , ex ) ;
}
try {
TransactionBuilder builder ;
builder = performInitialized ( " Sending air quality index " ) ;
int length = 8 ;
String aqiString = " (n/a) " ;
if ( supportsConditionString ) {
length + = aqiString . getBytes ( ) . length + 1 ;
}
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 4 ) ;
buf . putInt ( weatherSpec . timestamp ) ;
buf . put ( ( byte ) ( tz_offset_hours * 4 ) ) ;
2020-04-08 12:02:47 +02:00
buf . putShort ( ( short ) - 1 ) ;
2019-08-25 09:55:23 +02:00
if ( supportsConditionString ) {
buf . put ( aqiString . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
}
2016-12-31 15:56:05 +01:00
2019-08-25 09:55:23 +02:00
if ( characteristicChunked ! = null ) {
writeToChunked ( builder , 1 , buf . array ( ) ) ;
} else {
builder . write ( getCharacteristic ( AmazfitBipService . UUID_CHARACTERISTIC_WEATHER ) , buf . array ( ) ) ;
}
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Error sending air quality " ) ;
}
try {
TransactionBuilder builder = performInitialized ( " Sending weather forecast " ) ;
2020-06-29 19:41:56 +02:00
if ( weatherSpec . forecasts . size ( ) > 6 ) { //TDOD: find out the limits for each device
weatherSpec . forecasts . subList ( 6 , weatherSpec . forecasts . size ( ) ) . clear ( ) ;
2020-06-29 19:02:30 +02:00
}
2019-08-25 09:55:23 +02:00
final byte NR_DAYS = ( byte ) ( 1 + weatherSpec . forecasts . size ( ) ) ;
int bytesPerDay = 4 ;
int conditionsLength = 0 ;
if ( supportsConditionString ) {
bytesPerDay = 5 ;
conditionsLength = weatherSpec . currentCondition . getBytes ( ) . length ;
for ( WeatherSpec . Forecast forecast : weatherSpec . forecasts ) {
conditionsLength + = Weather . getConditionString ( forecast . conditionCode ) . getBytes ( ) . length ;
}
}
int length = 7 + bytesPerDay * NR_DAYS + conditionsLength ;
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 1 ) ;
buf . putInt ( weatherSpec . timestamp ) ;
buf . put ( ( byte ) ( tz_offset_hours * 4 ) ) ;
buf . put ( NR_DAYS ) ;
byte condition = HuamiWeatherConditions . mapToAmazfitBipWeatherCode ( weatherSpec . currentConditionCode ) ;
buf . put ( condition ) ;
buf . put ( condition ) ;
int todayMaxTemp = weatherSpec . todayMaxTemp - 273 ;
int todayMinTemp = weatherSpec . todayMinTemp - 273 ;
if ( unit = = MiBandConst . DistanceUnit . IMPERIAL ) {
todayMaxTemp = ( int ) WeatherUtils . celsiusToFahrenheit ( todayMaxTemp ) ;
todayMinTemp = ( int ) WeatherUtils . celsiusToFahrenheit ( todayMinTemp ) ;
}
buf . put ( ( byte ) todayMaxTemp ) ;
buf . put ( ( byte ) todayMinTemp ) ;
if ( supportsConditionString ) {
buf . put ( weatherSpec . currentCondition . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
}
for ( WeatherSpec . Forecast forecast : weatherSpec . forecasts ) {
condition = HuamiWeatherConditions . mapToAmazfitBipWeatherCode ( forecast . conditionCode ) ;
buf . put ( condition ) ;
buf . put ( condition ) ;
int forecastMaxTemp = forecast . maxTemp - 273 ;
int forecastMinTemp = forecast . minTemp - 273 ;
if ( unit = = MiBandConst . DistanceUnit . IMPERIAL ) {
forecastMaxTemp = ( int ) WeatherUtils . celsiusToFahrenheit ( forecastMaxTemp ) ;
forecastMinTemp = ( int ) WeatherUtils . celsiusToFahrenheit ( forecastMinTemp ) ;
}
buf . put ( ( byte ) forecastMaxTemp ) ;
buf . put ( ( byte ) forecastMinTemp ) ;
if ( supportsConditionString ) {
buf . put ( Weather . getConditionString ( forecast . conditionCode ) . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
}
}
if ( characteristicChunked ! = null ) {
writeToChunked ( builder , 1 , buf . array ( ) ) ;
} else {
builder . write ( getCharacteristic ( AmazfitBipService . UUID_CHARACTERISTIC_WEATHER ) , buf . array ( ) ) ;
}
builder . queue ( getQueue ( ) ) ;
} catch ( Exception ex ) {
LOG . error ( " Error sending weather forecast " , ex ) ;
}
try {
TransactionBuilder builder ;
builder = performInitialized ( " Sending forecast location " ) ;
int length = 2 + weatherSpec . location . getBytes ( ) . length ;
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 8 ) ;
buf . put ( weatherSpec . location . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
if ( characteristicChunked ! = null ) {
2019-08-31 22:14:50 +02:00
writeToChunked ( builder , 1 , buf . array ( ) ) ;
2019-08-25 09:55:23 +02:00
} else {
builder . write ( getCharacteristic ( AmazfitBipService . UUID_CHARACTERISTIC_WEATHER ) , buf . array ( ) ) ;
}
builder . queue ( getQueue ( ) ) ;
} catch ( Exception ex ) {
LOG . error ( " Error sending current forecast location " , ex ) ;
}
2020-09-30 18:16:25 +02:00
2020-09-30 18:47:19 +02:00
if ( supportsSunriseSunsetWindHumidity ( ) ) {
try {
TransactionBuilder builder ;
builder = performInitialized ( " Sending wind/humidity " ) ;
2021-04-21 17:12:20 +02:00
String windString = this . windSpeedString ( weatherSpec ) ;
2020-09-30 18:47:19 +02:00
String humidityString = weatherSpec . currentHumidity + " % " ;
int length = 8 + windString . getBytes ( ) . length + humidityString . getBytes ( ) . length ;
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 64 ) ;
buf . putInt ( weatherSpec . timestamp ) ;
buf . put ( ( byte ) ( tz_offset_hours * 4 ) ) ;
buf . put ( windString . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
buf . put ( humidityString . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
writeToChunked ( builder , 1 , buf . array ( ) ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( Exception ex ) {
LOG . error ( " Error sending wind/humidity " , ex ) ;
}
2021-04-21 17:12:20 +02:00
float [ ] longlat = GBApplication . getGBPrefs ( ) . getLongLat ( getContext ( ) ) ;
float longitude = longlat [ 0 ] ;
float latitude = longlat [ 1 ] ;
if ( longitude ! = 0 & & latitude ! = 0 ) {
final GregorianCalendar dateTimeToday = new GregorianCalendar ( ) ;
GregorianCalendar [ ] sunriseTransitSet = SPA . calculateSunriseTransitSet ( dateTimeToday , latitude , longitude , DeltaT . estimate ( dateTimeToday ) ) ;
try {
TransactionBuilder builder ;
builder = performInitialized ( " Sending sunrise/sunset " ) ;
ByteBuffer buf = ByteBuffer . allocate ( 10 ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 16 ) ;
buf . putInt ( weatherSpec . timestamp ) ;
buf . put ( ( byte ) ( tz_offset_hours * 4 ) ) ;
buf . put ( ( byte ) sunriseTransitSet [ 0 ] . get ( GregorianCalendar . HOUR_OF_DAY ) ) ;
buf . put ( ( byte ) sunriseTransitSet [ 0 ] . get ( GregorianCalendar . MINUTE ) ) ;
buf . put ( ( byte ) sunriseTransitSet [ 2 ] . get ( GregorianCalendar . HOUR_OF_DAY ) ) ;
buf . put ( ( byte ) sunriseTransitSet [ 2 ] . get ( GregorianCalendar . MINUTE ) ) ;
writeToChunked ( builder , 1 , buf . array ( ) ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( Exception ex ) {
LOG . error ( " Error sending sunset/sunrise " , ex ) ;
2020-09-30 18:16:25 +02:00
}
}
}
2016-12-31 15:56:05 +01:00
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setDateDisplay ( TransactionBuilder builder ) {
2019-05-23 21:33:35 +02:00
DateTimeDisplay dateTimeDisplay = HuamiCoordinator . getDateDisplay ( getContext ( ) , gbDevice . getAddress ( ) ) ;
2016-11-13 20:47:24 +01:00
LOG . info ( " Setting date display to " + dateTimeDisplay ) ;
switch ( dateTimeDisplay ) {
2016-11-13 01:42:55 +01:00
case TIME :
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . DATEFORMAT_TIME ) ;
2016-11-13 01:42:55 +01:00
break ;
case DATE_TIME :
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . DATEFORMAT_DATE_TIME ) ;
2016-11-13 01:42:55 +01:00
break ;
}
return this ;
}
2019-07-16 23:48:08 +02:00
protected HuamiSupport setDateFormat ( TransactionBuilder builder ) {
String dateFormat = GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) . getString ( " dateformat " , " MM/dd/yyyy " ) ;
if ( dateFormat = = null ) {
2022-08-18 23:03:28 +02:00
return this ;
2019-07-16 23:48:08 +02:00
}
switch ( dateFormat ) {
case " MM/dd/yyyy " :
case " dd.MM.yyyy " :
case " dd/MM/yyyy " :
byte [ ] command = HuamiService . DATEFORMAT_DATE_MM_DD_YYYY ;
System . arraycopy ( dateFormat . getBytes ( ) , 0 , command , 3 , 10 ) ;
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , command ) ;
2019-07-16 23:48:08 +02:00
break ;
default :
LOG . warn ( " unsupported date format " + dateFormat ) ;
}
return this ;
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setTimeFormat ( TransactionBuilder builder ) {
2020-01-04 23:40:50 +01:00
GBPrefs gbPrefs = new GBPrefs ( new Prefs ( GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ) ) ;
String timeFormat = gbPrefs . getTimeFormat ( ) ;
LOG . info ( " Setting time format to " + timeFormat ) ;
2022-08-18 23:03:28 +02:00
if ( timeFormat . equals ( DeviceSettingsPreferenceConst . PREF_TIMEFORMAT_24H ) ) {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . DATEFORMAT_TIME_24_HOURS ) ;
2017-03-03 22:32:54 +01:00
} else {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . DATEFORMAT_TIME_12_HOURS ) ;
2017-03-03 22:14:28 +01:00
}
return this ;
}
2022-07-30 21:37:21 +02:00
protected HuamiSupport setGoalNotification ( TransactionBuilder builder ) {
2022-05-14 22:08:32 +02:00
boolean enable = HuamiCoordinator . getGoalNotification ( gbDevice . getAddress ( ) ) ;
2017-07-15 00:40:58 +02:00
LOG . info ( " Setting goal notification to " + enable ) ;
if ( enable ) {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_ENABLE_GOAL_NOTIFICATION ) ;
2017-07-15 00:40:58 +02:00
} else {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_DISABLE_GOAL_NOTIFICATION ) ;
2017-07-15 00:40:58 +02:00
}
return this ;
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setActivateDisplayOnLiftWrist ( TransactionBuilder builder ) {
2019-06-05 16:00:18 +02:00
ActivateDisplayOnLift displayOnLift = HuamiCoordinator . getActivateDisplayOnLiftWrist ( getContext ( ) , gbDevice . getAddress ( ) ) ;
2018-03-23 23:27:03 +01:00
LOG . info ( " Setting activate display on lift wrist to " + displayOnLift ) ;
switch ( displayOnLift ) {
case ON :
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST ) ;
2018-03-23 23:27:03 +01:00
break ;
case OFF :
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_DISABLE_DISPLAY_ON_LIFT_WRIST ) ;
2018-03-23 23:27:03 +01:00
break ;
case SCHEDULED :
2018-08-02 10:55:30 +02:00
byte [ ] cmd = HuamiService . COMMAND_SCHEDULE_DISPLAY_ON_LIFT_WRIST . clone ( ) ;
2018-03-23 23:27:03 +01:00
Calendar calendar = GregorianCalendar . getInstance ( ) ;
2019-06-05 16:00:18 +02:00
Date start = HuamiCoordinator . getDisplayOnLiftStart ( gbDevice . getAddress ( ) ) ;
2018-03-23 23:27:03 +01:00
calendar . setTime ( start ) ;
cmd [ 4 ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
cmd [ 5 ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
2019-06-05 16:00:18 +02:00
Date end = HuamiCoordinator . getDisplayOnLiftEnd ( gbDevice . getAddress ( ) ) ;
2018-03-23 23:27:03 +01:00
calendar . setTime ( end ) ;
cmd [ 6 ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
cmd [ 7 ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , cmd ) ;
2023-03-19 23:11:39 +01:00
break ;
default :
LOG . warn ( " Unknown display on lift mode {} " , displayOnLift ) ;
2016-11-13 21:33:43 +01:00
}
return this ;
}
2022-05-09 14:57:44 +02:00
protected HuamiSupport setActivateDisplayOnLiftWristSensitivity ( TransactionBuilder builder ) {
final ActivateDisplayOnLiftSensitivity sensitivity = HuamiCoordinator . getDisplayOnLiftSensitivity ( gbDevice . getAddress ( ) ) ;
LOG . info ( " Setting activate display on lift wrist sensitivity to " + sensitivity ) ;
switch ( sensitivity ) {
case SENSITIVE :
writeToConfiguration ( builder , HuamiService . COMMAND_DISPLAY_ON_LIFT_WRIST_SPEED_SENSITIVE ) ;
break ;
case NORMAL :
default :
writeToConfiguration ( builder , HuamiService . COMMAND_DISPLAY_ON_LIFT_WRIST_SPEED_NORMAL ) ;
break ;
}
return this ;
}
2018-08-01 22:56:01 +02:00
protected HuamiSupport setDisplayItems ( TransactionBuilder builder ) {
2020-05-12 22:42:19 +02:00
SharedPreferences prefs = GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ;
Set < String > pages = prefs . getStringSet ( HuamiConst . PREF_DISPLAY_ITEMS , new HashSet < > ( Arrays . asList ( getContext ( ) . getResources ( ) . getStringArray ( R . array . pref_mi2_display_items_default ) ) ) ) ;
2017-07-09 16:17:13 +02:00
LOG . info ( " Setting display items to " + ( pages = = null ? " none " : pages ) ) ;
2018-08-02 10:55:30 +02:00
byte [ ] data = HuamiService . COMMAND_CHANGE_SCREENS . clone ( ) ;
2017-07-09 16:17:13 +02:00
2017-07-13 23:26:25 +02:00
if ( pages ! = null ) {
if ( pages . contains ( MiBandConst . PREF_MI2_DISPLAY_ITEM_STEPS ) ) {
2018-08-02 10:55:30 +02:00
data [ HuamiService . SCREEN_CHANGE_BYTE ] | = HuamiService . DISPLAY_ITEM_BIT_STEPS ;
2017-07-13 23:26:25 +02:00
}
if ( pages . contains ( MiBandConst . PREF_MI2_DISPLAY_ITEM_DISTANCE ) ) {
2018-08-02 10:55:30 +02:00
data [ HuamiService . SCREEN_CHANGE_BYTE ] | = HuamiService . DISPLAY_ITEM_BIT_DISTANCE ;
2017-07-13 23:26:25 +02:00
}
if ( pages . contains ( MiBandConst . PREF_MI2_DISPLAY_ITEM_CALORIES ) ) {
2018-08-02 10:55:30 +02:00
data [ HuamiService . SCREEN_CHANGE_BYTE ] | = HuamiService . DISPLAY_ITEM_BIT_CALORIES ;
2017-07-13 23:26:25 +02:00
}
if ( pages . contains ( MiBandConst . PREF_MI2_DISPLAY_ITEM_HEART_RATE ) ) {
2018-08-02 10:55:30 +02:00
data [ HuamiService . SCREEN_CHANGE_BYTE ] | = HuamiService . DISPLAY_ITEM_BIT_HEART_RATE ;
2017-07-13 23:26:25 +02:00
}
if ( pages . contains ( MiBandConst . PREF_MI2_DISPLAY_ITEM_BATTERY ) ) {
2018-08-02 10:55:30 +02:00
data [ HuamiService . SCREEN_CHANGE_BYTE ] | = HuamiService . DISPLAY_ITEM_BIT_BATTERY ;
2017-07-13 23:26:25 +02:00
}
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , data ) ;
2017-07-09 16:17:13 +02:00
}
2017-07-13 23:26:25 +02:00
return this ;
}
2017-07-09 16:17:13 +02:00
2020-11-07 10:19:28 +01:00
protected HuamiSupport setDisplayItemsOld ( TransactionBuilder builder , boolean isShortcuts , int defaultSettings , Map < String , Integer > keyPosMap ) {
SharedPreferences prefs = GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ;
String pages ;
List < String > enabledList ;
if ( isShortcuts ) {
pages = prefs . getString ( HuamiConst . PREF_SHORTCUTS_SORTABLE , null ) ;
LOG . info ( " Setting shortcuts " ) ;
} else {
pages = prefs . getString ( HuamiConst . PREF_DISPLAY_ITEMS_SORTABLE , null ) ;
LOG . info ( " Setting menu items " ) ;
}
if ( pages = = null ) {
enabledList = Arrays . asList ( getContext ( ) . getResources ( ) . getStringArray ( defaultSettings ) ) ;
} else {
enabledList = Arrays . asList ( pages . split ( " , " ) ) ;
}
LOG . info ( " enabled items " + enabledList ) ;
2020-11-07 21:03:04 +01:00
byte [ ] command ;
2020-11-07 10:19:28 +01:00
2020-11-07 21:03:04 +01:00
if ( isShortcuts ) {
command = new byte [ keyPosMap . size ( ) * 2 + 1 ] ;
command [ 0 ] = 0x10 ;
int pos = 1 ;
int index = 0 ;
for ( String key : enabledList ) {
Integer id = keyPosMap . get ( key ) ;
if ( id ! = null ) {
command [ pos + + ] = ( byte ) ( 0x80 | index + + ) ;
command [ pos + + ] = id . byteValue ( ) ;
}
2020-11-07 10:19:28 +01:00
}
2020-11-07 21:03:04 +01:00
for ( Map . Entry < String , Integer > entry : keyPosMap . entrySet ( ) ) {
String key = entry . getKey ( ) ;
int id = entry . getValue ( ) ;
2020-11-07 10:19:28 +01:00
2020-11-07 21:03:04 +01:00
if ( ! enabledList . contains ( key ) ) {
command [ pos + + ] = ( byte ) index + + ;
command [ pos + + ] = ( byte ) id ;
}
}
} else {
command = new byte [ keyPosMap . size ( ) + 4 ] ;
command [ 0 ] = ENDPOINT_DISPLAY_ITEMS ;
byte index = 1 ;
int enabled_mask = DISPLAY_ITEM_BIT_CLOCK ;
// it seem that we first have to put all ENABLED items into the array, oder does matter
for ( String key : enabledList ) {
Integer id = keyPosMap . get ( key ) ;
if ( id ! = null ) {
enabled_mask | = ( 1 < < id . byteValue ( ) ) ;
command [ 3 + id ] = index + + ;
}
}
// And then all DISABLED ones, order does not matter
for ( Map . Entry < String , Integer > entry : keyPosMap . entrySet ( ) ) {
String key = entry . getKey ( ) ;
int id = entry . getValue ( ) ;
if ( ! enabledList . contains ( key ) ) {
command [ 3 + id ] = index + + ;
}
2020-11-07 10:19:28 +01:00
}
2020-11-07 21:03:04 +01:00
command [ 1 ] = ( byte ) ( enabled_mask & 0xff ) ;
command [ 2 ] = ( byte ) ( ( enabled_mask > > 8 & 0xff ) ) ;
}
2020-11-07 10:19:28 +01:00
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , command ) ;
2020-11-07 10:19:28 +01:00
return this ;
}
2021-03-24 00:42:25 +01:00
protected HuamiSupport setDisplayItemsNew ( TransactionBuilder builder , boolean isShortcuts , boolean forceWatchface , int defaultSettings ) {
2020-10-20 20:12:08 +02:00
SharedPreferences prefs = GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ;
2020-11-06 21:47:54 +01:00
String pages ;
2021-03-24 10:49:46 +01:00
ArrayList < String > enabledList ;
2020-10-21 21:43:48 +02:00
byte menuType ;
if ( isShortcuts ) {
menuType = ( byte ) 0xfd ;
2020-11-06 21:47:54 +01:00
pages = prefs . getString ( HuamiConst . PREF_SHORTCUTS_SORTABLE , null ) ;
LOG . info ( " Setting shortcuts " ) ;
2020-10-21 21:43:48 +02:00
} else {
menuType = ( byte ) 0xff ;
2020-11-06 21:47:54 +01:00
pages = prefs . getString ( HuamiConst . PREF_DISPLAY_ITEMS_SORTABLE , null ) ;
LOG . info ( " Setting menu items " ) ;
}
if ( pages = = null ) {
2021-03-24 10:49:46 +01:00
enabledList = new ArrayList < > ( Arrays . asList ( getContext ( ) . getResources ( ) . getStringArray ( defaultSettings ) ) ) ;
2020-11-06 21:47:54 +01:00
} else {
2021-03-24 10:49:46 +01:00
enabledList = new ArrayList < > ( Arrays . asList ( pages . split ( " , " ) ) ) ;
2020-10-21 21:43:48 +02:00
}
2021-03-24 00:42:25 +01:00
if ( forceWatchface ) {
enabledList . add ( 0 , " watchface " ) ;
}
2020-11-06 21:47:54 +01:00
LOG . info ( " enabled items " + enabledList ) ;
2021-03-24 00:42:25 +01:00
byte [ ] command = new byte [ enabledList . size ( ) * 4 + 1 ] ;
2020-11-06 21:47:54 +01:00
command [ 0 ] = 0x1e ;
2020-11-10 11:32:50 +01:00
2020-11-06 21:47:54 +01:00
int pos = 1 ;
int index = 0 ;
2020-11-10 11:32:50 +01:00
2020-11-06 21:47:54 +01:00
for ( String key : enabledList ) {
2020-11-10 21:56:00 +01:00
Integer id = HuamiMenuType . idLookup . get ( key ) ;
2020-11-06 21:47:54 +01:00
if ( id ! = null ) {
command [ pos + + ] = ( byte ) index + + ;
command [ pos + + ] = 0x00 ;
command [ pos + + ] = menuType ;
command [ pos + + ] = id . byteValue ( ) ;
2020-10-20 20:12:08 +02:00
}
2020-11-06 21:47:54 +01:00
}
2021-09-01 15:25:39 +02:00
2020-11-06 21:47:54 +01:00
writeToChunked ( builder , 2 , command ) ;
2020-10-20 20:12:08 +02:00
return this ;
}
2020-05-13 10:15:12 +02:00
protected HuamiSupport setShortcuts ( TransactionBuilder builder ) {
return this ;
}
2022-05-09 15:05:08 +02:00
protected HuamiSupport setWorkoutActivityTypes ( final TransactionBuilder builder ) {
final SharedPreferences prefs = GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ;
2022-09-25 11:10:38 +02:00
final List < String > defaultActivityTypes = Arrays . asList ( HuamiWorkoutScreenActivityType . Freestyle . name ( ) . toLowerCase ( Locale . ROOT ) ) ;
2022-05-09 15:05:08 +02:00
final String activityTypesPref = prefs . getString ( HuamiConst . PREF_WORKOUT_ACTIVITY_TYPES_SORTABLE , null ) ;
final List < String > enabledActivityTypes ;
2022-09-25 11:10:38 +02:00
if ( activityTypesPref = = null | | activityTypesPref . equals ( " " ) | | activityTypesPref . equals ( " more " ) ) {
2022-05-09 15:05:08 +02:00
enabledActivityTypes = defaultActivityTypes ;
} else {
enabledActivityTypes = Arrays . asList ( activityTypesPref . split ( " , " ) ) ;
}
LOG . info ( " Setting workout types to {} " , enabledActivityTypes ) ;
2022-09-25 11:10:38 +02:00
int workoutCount = enabledActivityTypes . size ( ) ;
if ( enabledActivityTypes . contains ( " more " ) ) {
// we shouldn't count the more item when it is present, since it isn't a real
// workout type and isn't sent to the device
workoutCount - - ;
}
final ByteBuffer command = ByteBuffer . allocate ( workoutCount * 3 + 2 ) ;
command . order ( ByteOrder . LITTLE_ENDIAN ) ;
command . putShort ( ( short ) workoutCount ) ;
2022-05-09 15:05:08 +02:00
2022-09-25 11:10:38 +02:00
// a value of 1 puts items in the main section, a value of 0 puts them in the more section
// by default items are put in the main section
byte section = 0x01 ;
2022-05-09 15:05:08 +02:00
for ( final String workoutType : enabledActivityTypes ) {
2022-09-25 11:10:38 +02:00
if ( workoutType . equals ( " more " ) ) {
// all items that follow the More separator are put in the more section
section = 0x00 ;
continue ;
}
byte code = HuamiWorkoutScreenActivityType . fromPrefValue ( workoutType ) . getCode ( ) ;
command . putShort ( code ) ;
command . put ( section ) ;
2022-05-09 15:05:08 +02:00
}
2022-09-25 11:10:38 +02:00
writeToChunked ( builder , 9 , command . array ( ) ) ;
2022-05-09 15:05:08 +02:00
return this ;
}
2021-03-24 20:02:48 +01:00
protected HuamiSupport setBeepSounds ( TransactionBuilder builder ) {
SharedPreferences prefs = GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ;
Set < String > sounds = prefs . getStringSet ( PREF_SOUNDS , new HashSet < > ( Arrays . asList ( getContext ( ) . getResources ( ) . getStringArray ( R . array . pref_amazfitneo_sounds_default ) ) ) ) ;
LOG . info ( " Setting sounds to " + ( sounds = = null ? " none " : sounds ) ) ;
if ( sounds ! = null ) {
2022-04-07 22:54:24 +02:00
final String [ ] soundOrder = new String [ ] { " button " , " calls " , " alarm " , " notifications " , " inactivity_warning " , " sms " , " email " , " goal " } ;
byte [ ] command = new byte [ ] { 0x3c , 0 , 0 , 0 , 1 , 0 , 0 , 2 , 0 , 0 , 3 , 0 , 0 , 4 , 0 , 0 , 5 , 0 , 0 , 6 , 0 , 0 , 7 , 0 , 0 } ;
2021-03-24 20:02:48 +01:00
int i = 3 ;
for ( String sound : soundOrder ) {
if ( sounds . contains ( sound ) ) {
command [ i ] = 1 ;
}
i + = 3 ;
}
writeToChunked ( builder , 2 , command ) ;
}
return this ;
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setRotateWristToSwitchInfo ( TransactionBuilder builder ) {
2019-06-05 16:00:18 +02:00
boolean enable = HuamiCoordinator . getRotateWristToSwitchInfo ( gbDevice . getAddress ( ) ) ;
2017-07-09 14:30:03 +02:00
LOG . info ( " Setting rotate wrist to cycle info to " + enable ) ;
if ( enable ) {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_ENABLE_ROTATE_WRIST_TO_SWITCH_INFO ) ;
2017-07-09 14:30:03 +02:00
} else {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_DISABLE_ROTATE_WRIST_TO_SWITCH_INFO ) ;
2017-07-09 14:30:03 +02:00
}
2017-07-09 16:17:13 +02:00
return this ;
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setDisplayCaller ( TransactionBuilder builder ) {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_ENABLE_DISPLAY_CALLER ) ;
2017-08-25 01:04:36 +02:00
return this ;
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setDoNotDisturb ( TransactionBuilder builder ) {
2019-05-24 23:46:20 +02:00
DoNotDisturb doNotDisturb = HuamiCoordinator . getDoNotDisturb ( gbDevice . getAddress ( ) ) ;
2021-12-12 00:15:33 +01:00
boolean doNotDisturbLiftWrist = HuamiCoordinator . getDoNotDisturbLiftWrist ( gbDevice . getAddress ( ) ) ;
LOG . info ( " Setting do not disturb to {}, wake on lift wrist {} " , doNotDisturb , doNotDisturbLiftWrist ) ;
byte [ ] data = null ;
2017-07-15 15:01:07 +02:00
switch ( doNotDisturb ) {
case OFF :
2021-12-12 00:15:33 +01:00
data = HuamiService . COMMAND_DO_NOT_DISTURB_OFF . clone ( ) ;
2017-07-15 15:01:07 +02:00
break ;
case AUTOMATIC :
2021-12-12 00:15:33 +01:00
data = HuamiService . COMMAND_DO_NOT_DISTURB_AUTOMATIC . clone ( ) ;
2017-07-15 15:01:07 +02:00
break ;
case SCHEDULED :
2021-12-12 00:15:33 +01:00
data = HuamiService . COMMAND_DO_NOT_DISTURB_SCHEDULED . clone ( ) ;
2017-07-15 15:01:07 +02:00
Calendar calendar = GregorianCalendar . getInstance ( ) ;
2019-05-24 23:46:20 +02:00
Date start = HuamiCoordinator . getDoNotDisturbStart ( gbDevice . getAddress ( ) ) ;
2017-07-15 15:01:07 +02:00
calendar . setTime ( start ) ;
2018-08-02 10:55:30 +02:00
data [ HuamiService . DND_BYTE_START_HOURS ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
data [ HuamiService . DND_BYTE_START_MINUTES ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
2017-07-15 15:01:07 +02:00
2019-05-24 23:46:20 +02:00
Date end = HuamiCoordinator . getDoNotDisturbEnd ( gbDevice . getAddress ( ) ) ;
2017-07-15 15:01:07 +02:00
calendar . setTime ( end ) ;
2018-08-02 10:55:30 +02:00
data [ HuamiService . DND_BYTE_END_HOURS ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
data [ HuamiService . DND_BYTE_END_MINUTES ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
2017-07-15 15:01:07 +02:00
break ;
}
2021-12-12 00:15:33 +01:00
if ( data ! = null ) {
if ( doNotDisturbLiftWrist & & doNotDisturb ! = DoNotDisturb . OFF ) {
data [ 1 ] & = ~ 0x80 ;
}
writeToConfiguration ( builder , data ) ;
}
2017-07-15 15:01:07 +02:00
return this ;
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setNightMode ( TransactionBuilder builder ) {
String nightMode = MiBand3Coordinator . getNightMode ( gbDevice . getAddress ( ) ) ;
LOG . info ( " Setting night mode to " + nightMode ) ;
switch ( nightMode ) {
case MiBandConst . PREF_NIGHT_MODE_SUNSET :
writeToConfiguration ( builder , MiBand3Service . COMMAND_NIGHT_MODE_SUNSET ) ;
break ;
case MiBandConst . PREF_NIGHT_MODE_OFF :
writeToConfiguration ( builder , MiBand3Service . COMMAND_NIGHT_MODE_OFF ) ;
break ;
case MiBandConst . PREF_NIGHT_MODE_SCHEDULED :
byte [ ] cmd = MiBand3Service . COMMAND_NIGHT_MODE_SCHEDULED . clone ( ) ;
Calendar calendar = GregorianCalendar . getInstance ( ) ;
Date start = MiBand3Coordinator . getNightModeStart ( gbDevice . getAddress ( ) ) ;
calendar . setTime ( start ) ;
cmd [ 2 ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
cmd [ 3 ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
Date end = MiBand3Coordinator . getNightModeEnd ( gbDevice . getAddress ( ) ) ;
calendar . setTime ( end ) ;
cmd [ 4 ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
cmd [ 5 ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
writeToConfiguration ( builder , cmd ) ;
break ;
default :
LOG . error ( " Invalid night mode: " + nightMode ) ;
break ;
}
return this ;
}
protected HuamiSupport setInactivityWarnings ( TransactionBuilder builder ) {
2022-05-14 22:08:32 +02:00
boolean enable = HuamiCoordinator . getInactivityWarnings ( gbDevice . getAddress ( ) ) ;
2017-07-15 22:48:26 +02:00
LOG . info ( " Setting inactivity warnings to " + enable ) ;
if ( enable ) {
2018-08-02 10:55:30 +02:00
byte [ ] data = HuamiService . COMMAND_ENABLE_INACTIVITY_WARNINGS . clone ( ) ;
2017-07-15 22:48:26 +02:00
2022-05-14 22:08:32 +02:00
int threshold = HuamiCoordinator . getInactivityWarningsThreshold ( gbDevice . getAddress ( ) ) ;
2018-08-02 10:55:30 +02:00
data [ HuamiService . INACTIVITY_WARNINGS_THRESHOLD ] = ( byte ) threshold ;
2017-07-15 22:48:26 +02:00
Calendar calendar = GregorianCalendar . getInstance ( ) ;
2022-05-14 22:08:32 +02:00
boolean enableDnd = HuamiCoordinator . getInactivityWarningsDnd ( gbDevice . getAddress ( ) ) ;
2017-07-15 22:48:26 +02:00
2022-05-14 22:08:32 +02:00
Date intervalStart = HuamiCoordinator . getInactivityWarningsStart ( gbDevice . getAddress ( ) ) ;
Date intervalEnd = HuamiCoordinator . getInactivityWarningsEnd ( gbDevice . getAddress ( ) ) ;
Date dndStart = HuamiCoordinator . getInactivityWarningsDndStart ( gbDevice . getAddress ( ) ) ;
Date dndEnd = HuamiCoordinator . getInactivityWarningsDndEnd ( gbDevice . getAddress ( ) ) ;
2017-07-15 22:48:26 +02:00
// The first interval always starts when the warnings interval starts
calendar . setTime ( intervalStart ) ;
2018-08-02 10:55:30 +02:00
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_1_START_HOURS ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_1_START_MINUTES ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
2017-07-15 22:48:26 +02:00
if ( enableDnd ) {
// The first interval ends when the dnd interval starts
calendar . setTime ( dndStart ) ;
2018-08-02 10:55:30 +02:00
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
2017-07-15 22:48:26 +02:00
// The second interval starts when the dnd interval ends
calendar . setTime ( dndEnd ) ;
2018-08-02 10:55:30 +02:00
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_2_START_HOURS ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_2_START_MINUTES ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
2017-07-15 22:48:26 +02:00
// ... and it ends when the warnings interval ends
calendar . setTime ( intervalEnd ) ;
2018-08-02 10:55:30 +02:00
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_2_END_HOURS ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_2_END_MINUTES ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
2017-07-15 22:48:26 +02:00
} else {
// No Dnd, use the first interval
calendar . setTime ( intervalEnd ) ;
2018-08-02 10:55:30 +02:00
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
2017-07-15 22:48:26 +02:00
}
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , data ) ;
2017-07-15 22:48:26 +02:00
} else {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_DISABLE_INACTIVITY_WARNINGS ) ;
2017-07-15 22:48:26 +02:00
}
return this ;
}
2022-08-09 14:30:21 +02:00
protected HuamiSupport setHourlyChime ( TransactionBuilder builder ) {
if ( ! supportsHourlyChime ( ) )
return this ;
boolean enable = HuamiCoordinator . getHourlyChime ( gbDevice . getAddress ( ) ) ;
LOG . info ( " Setting hourly chime to " + enable ) ;
if ( enable ) {
byte [ ] data = HuamiService . COMMAND_ENABLE_HOURLY_CHIME . clone ( ) ;
Calendar calendar = GregorianCalendar . getInstance ( ) ;
Date intervalStart = HuamiCoordinator . getHourlyChimeStart ( gbDevice . getAddress ( ) ) ;
Date intervalEnd = HuamiCoordinator . getHourlyChimeEnd ( gbDevice . getAddress ( ) ) ;
calendar . setTime ( intervalStart ) ;
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_1_START_HOURS ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_1_START_MINUTES ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
calendar . setTime ( intervalEnd ) ;
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
data [ HuamiService . INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
writeToConfiguration ( builder , data ) ;
} else {
writeToConfiguration ( builder , HuamiService . COMMAND_DISABLE_HOURLY_CHIME ) ;
}
return this ;
}
2022-08-18 23:03:28 +02:00
public boolean supportsHourlyChime ( ) {
return false ;
}
2022-08-09 14:30:21 +02:00
2022-08-18 23:03:28 +02:00
protected HuamiSupport setDisconnectNotification ( TransactionBuilder builder ) {
2019-05-22 00:42:22 +02:00
DisconnectNotificationSetting disconnectNotificationSetting = HuamiCoordinator . getDisconnectNotificationSetting ( getContext ( ) , gbDevice . getAddress ( ) ) ;
2019-02-13 13:06:42 +01:00
LOG . info ( " Setting disconnect notification to " + disconnectNotificationSetting ) ;
switch ( disconnectNotificationSetting ) {
case ON :
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_ENABLE_DISCONNECT_NOTIFCATION ) ;
2019-02-13 13:06:42 +01:00
break ;
case OFF :
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_DISABLE_DISCONNECT_NOTIFCATION ) ;
2019-02-13 13:06:42 +01:00
break ;
case SCHEDULED :
byte [ ] cmd = HuamiService . COMMAND_ENABLE_DISCONNECT_NOTIFCATION . clone ( ) ;
Calendar calendar = GregorianCalendar . getInstance ( ) ;
2019-05-22 10:49:59 +02:00
Date start = HuamiCoordinator . getDisconnectNotificationStart ( gbDevice . getAddress ( ) ) ;
2019-02-13 13:06:42 +01:00
calendar . setTime ( start ) ;
cmd [ 4 ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
cmd [ 5 ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
2019-05-22 10:49:59 +02:00
Date end = HuamiCoordinator . getDisconnectNotificationEnd ( gbDevice . getAddress ( ) ) ;
2019-02-13 13:06:42 +01:00
calendar . setTime ( end ) ;
cmd [ 6 ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
cmd [ 7 ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , cmd ) ;
2019-02-13 13:06:42 +01:00
}
return this ;
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setDistanceUnit ( TransactionBuilder builder ) {
2017-10-23 14:46:08 +02:00
MiBandConst . DistanceUnit unit = HuamiCoordinator . getDistanceUnit ( ) ;
2017-10-02 22:23:17 +02:00
LOG . info ( " Setting distance unit to " + unit ) ;
if ( unit = = MiBandConst . DistanceUnit . METRIC ) {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_DISTANCE_UNIT_METRIC ) ;
2017-10-02 22:23:17 +02:00
} else {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_DISTANCE_UNIT_IMPERIAL ) ;
2017-10-02 22:23:17 +02:00
}
return this ;
}
2019-05-22 00:42:22 +02:00
protected HuamiSupport setBandScreenUnlock ( TransactionBuilder builder ) {
boolean enable = MiBand3Coordinator . getBandScreenUnlock ( gbDevice . getAddress ( ) ) ;
LOG . info ( " Setting band screen unlock to " + enable ) ;
if ( enable ) {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , MiBand3Service . COMMAND_ENABLE_BAND_SCREEN_UNLOCK ) ;
2019-05-22 00:42:22 +02:00
} else {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , MiBand3Service . COMMAND_DISABLE_BAND_SCREEN_UNLOCK ) ;
2019-05-22 00:42:22 +02:00
}
return this ;
}
2019-05-20 16:36:06 +02:00
protected HuamiSupport setLanguage ( TransactionBuilder builder ) {
String localeString = GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) . getString ( " language " , " auto " ) ;
if ( localeString = = null | | localeString . equals ( " auto " ) ) {
String language = Locale . getDefault ( ) . getLanguage ( ) ;
String country = Locale . getDefault ( ) . getCountry ( ) ;
if ( country = = null ) {
// sometimes country is null, no idea why, guess it.
country = language ;
}
localeString = language + " _ " + country . toUpperCase ( ) ;
}
LOG . info ( " Setting device to locale: " + localeString ) ;
final byte [ ] command_new = HuamiService . COMMAND_SET_LANGUAGE_NEW_TEMPLATE . clone ( ) ;
System . arraycopy ( localeString . getBytes ( ) , 0 , command_new , 3 , localeString . getBytes ( ) . length ) ;
byte [ ] command_old ;
switch ( localeString . substring ( 0 , 2 ) ) {
case " es " :
command_old = AmazfitBipService . COMMAND_SET_LANGUAGE_SPANISH ;
break ;
case " zh " :
if ( localeString . equals ( " zh_CN " ) ) {
command_old = AmazfitBipService . COMMAND_SET_LANGUAGE_SIMPLIFIED_CHINESE ;
} else {
command_old = AmazfitBipService . COMMAND_SET_LANGUAGE_TRADITIONAL_CHINESE ;
}
break ;
default :
command_old = AmazfitBipService . COMMAND_SET_LANGUAGE_ENGLISH ;
}
2022-08-18 23:03:28 +02:00
if ( force2021Protocol ( ) ) {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , command_new ) ;
} else {
final byte [ ] finalCommand_old = command_old ;
builder . add ( new ConditionalWriteAction ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) ) {
@Override
protected byte [ ] checkCondition ( ) {
if ( ( gbDevice . getType ( ) = = DeviceType . AMAZFITBIP & & new Version ( gbDevice . getFirmwareVersion ( ) ) . compareTo ( new Version ( " 0.1.0.77 " ) ) < 0 ) | |
( gbDevice . getType ( ) = = DeviceType . AMAZFITCOR & & new Version ( gbDevice . getFirmwareVersion ( ) ) . compareTo ( new Version ( " 1.0.7.23 " ) ) < 0 ) ) {
return finalCommand_old ;
} else {
return command_new ;
}
2019-05-20 16:36:06 +02:00
}
2021-09-01 13:38:48 +02:00
} ) ;
}
2019-05-20 16:36:06 +02:00
return this ;
}
2021-12-13 10:08:39 +01:00
/ *
Some newer devices seem to support setting the language by id again instead of a locale string
Amazfit Bip U and GTS 2 mini tested so far
* /
protected HuamiSupport setLanguageByIdNew ( TransactionBuilder builder ) {
2022-08-18 23:03:28 +02:00
final byte [ ] command = new byte [ ] { 0x06 , 0x3b , 0x00 , getLanguageId ( ) , 0x03 } ;
writeToConfiguration ( builder , command ) ;
return this ;
}
protected byte getLanguageId ( ) {
2021-12-13 10:08:39 +01:00
byte language_code = 0x02 ; // english default
String localeString = GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) . getString ( " language " , " auto " ) ;
if ( localeString = = null | | localeString . equals ( " auto " ) ) {
String language = Locale . getDefault ( ) . getLanguage ( ) ;
String country = Locale . getDefault ( ) . getCountry ( ) ;
localeString = language + " _ " + country . toUpperCase ( ) ;
}
Integer id = HuamiLanguageType . idLookup . get ( localeString ) ;
if ( id ! = null ) {
language_code = id . byteValue ( ) ;
}
2022-08-18 23:03:28 +02:00
return language_code ;
2021-12-13 10:08:39 +01:00
}
2019-08-27 11:13:45 +02:00
2022-08-18 23:03:28 +02:00
protected HuamiSupport setExposeHRThirdParty ( TransactionBuilder builder ) {
2019-08-27 11:13:45 +02:00
boolean enable = HuamiCoordinator . getExposeHRThirdParty ( gbDevice . getAddress ( ) ) ;
LOG . info ( " Setting exposure of HR to third party apps to: " + enable ) ;
if ( enable ) {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_ENBALE_HR_CONNECTION ) ;
2019-08-27 11:13:45 +02:00
} else {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_DISABLE_HR_CONNECTION ) ;
2019-08-27 11:13:45 +02:00
}
return this ;
}
2022-08-18 23:03:28 +02:00
protected HuamiSupport setBtConnectedAdvertising ( TransactionBuilder builder ) {
2020-12-06 00:09:19 +01:00
boolean enable = HuamiCoordinator . getBtConnectedAdvertising ( gbDevice . getAddress ( ) ) ;
LOG . info ( " Setting connected advertisement to: " + enable ) ;
if ( enable ) {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_ENABLE_BT_CONNECTED_ADVERTISEMENT ) ;
2020-12-06 00:09:19 +01:00
} else {
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_DISABLE_BT_CONNECTED_ADVERTISEMENT ) ;
2020-12-06 00:09:19 +01:00
}
return this ;
}
2020-04-27 22:55:17 +02:00
protected void writeToChunked ( TransactionBuilder builder , int type , byte [ ] data ) {
2022-08-18 23:03:28 +02:00
if ( force2021Protocol ( ) & & type > 0 ) {
2021-09-03 15:50:42 +02:00
boolean encrypt = true ;
if ( type = = 1 & & ( data [ 1 ] = = 2 ) ) { // don't encypt current weather
encrypt = false ;
}
2021-09-01 15:25:39 +02:00
byte [ ] command = ArrayUtils . addAll ( new byte [ ] { 0x00 , 0x00 , ( byte ) ( 0xc0 | type ) , 0x00 } , data ) ;
2022-08-18 23:03:28 +02:00
writeToChunked2021 ( builder , Huami2021Service . CHUNKED2021_ENDPOINT_COMPAT , command , encrypt ) ;
2021-09-01 15:25:39 +02:00
} else {
writeToChunkedOld ( builder , type , data ) ;
}
}
protected void writeToChunkedOld ( TransactionBuilder builder , int type , byte [ ] data ) {
2019-12-26 23:05:13 +01:00
final int MAX_CHUNKLENGTH = mMTU - 6 ;
2018-07-21 17:18:08 +02:00
int remaining = data . length ;
byte count = 0 ;
while ( remaining > 0 ) {
int copybytes = Math . min ( remaining , MAX_CHUNKLENGTH ) ;
byte [ ] chunk = new byte [ copybytes + 3 ] ;
byte flags = 0 ;
if ( remaining < = MAX_CHUNKLENGTH ) {
flags | = 0x80 ; // last chunk
if ( count = = 0 ) {
flags | = 0x40 ; // weird but true
}
} else if ( count > 0 ) {
flags | = 0x40 ; // consecutive chunk
}
chunk [ 0 ] = 0 ;
chunk [ 1 ] = ( byte ) ( flags | type ) ;
chunk [ 2 ] = ( byte ) ( count & 0xff ) ;
System . arraycopy ( data , count + + * MAX_CHUNKLENGTH , chunk , 3 , copybytes ) ;
builder . write ( characteristicChunked , chunk ) ;
remaining - = copybytes ;
}
}
2022-10-29 00:30:28 +02:00
public void writeToChunked2021 ( TransactionBuilder builder , short type , byte data , boolean encrypt ) {
2022-10-22 21:53:45 +02:00
writeToChunked2021 ( builder , type , new byte [ ] { data } , encrypt ) ;
}
2022-10-29 00:30:28 +02:00
public void writeToChunked2021 ( TransactionBuilder builder , short type , byte [ ] data , boolean encrypt ) {
2022-08-18 23:03:28 +02:00
huami2021ChunkedEncoder . write ( builder , type , data , force2021Protocol ( ) , encrypt ) ;
}
2021-12-23 18:06:58 +01:00
2022-10-29 00:30:28 +02:00
public void writeToChunked2021 ( final String taskName , short type , byte data , boolean encrypt ) {
2022-10-22 21:53:45 +02:00
writeToChunked2021 ( taskName , type , new byte [ ] { data } , encrypt ) ;
}
2022-10-29 00:30:28 +02:00
public void writeToChunked2021 ( final String taskName , short type , byte [ ] data , boolean encrypt ) {
2022-08-18 23:03:28 +02:00
try {
final TransactionBuilder builder = performInitialized ( taskName ) ;
writeToChunked2021 ( builder , type , data , encrypt ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( final Exception e ) {
LOG . error ( " Failed to " + taskName , e ) ;
2021-08-16 12:36:14 +02:00
}
}
2019-12-27 22:19:17 +01:00
2021-09-02 13:26:35 +02:00
public void writeToConfiguration ( TransactionBuilder builder , byte [ ] data ) {
2022-08-18 23:03:28 +02:00
if ( force2021Protocol ( ) ) {
2021-09-01 13:38:48 +02:00
data = ArrayUtils . insert ( 0 , data , ( byte ) 1 ) ;
2022-08-18 23:03:28 +02:00
writeToChunked2021 ( builder , Huami2021Service . CHUNKED2021_ENDPOINT_COMPAT , data , true ) ;
2021-09-01 13:38:48 +02:00
} else {
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , data ) ;
}
}
2019-12-27 22:19:17 +01:00
protected HuamiSupport requestGPSVersion ( TransactionBuilder builder ) {
LOG . info ( " Requesting GPS version " ) ;
2021-09-01 13:38:48 +02:00
writeToConfiguration ( builder , HuamiService . COMMAND_REQUEST_GPS_VERSION ) ;
2019-12-27 22:19:17 +01:00
return this ;
}
2022-08-12 15:57:24 +02:00
protected HuamiSupport requestAlarms ( TransactionBuilder builder ) {
2019-12-27 22:19:17 +01:00
LOG . info ( " Requesting alarms " ) ;
2022-01-21 12:48:36 +01:00
//FIXME: on older devices only the first one works, and on newer only the last is sufficient
2022-01-20 10:09:23 +01:00
writeToConfiguration ( builder , HuamiService . COMMAND_REQUEST_ALARMS ) ;
writeToConfiguration ( builder , HuamiService . COMMAND_REQUEST_ALARMS_WITH_TIMES ) ;
2019-12-27 22:19:17 +01:00
return this ;
}
2019-09-14 00:05:39 +02:00
@Override
public String customStringFilter ( String inputString ) {
if ( HuamiCoordinator . getUseCustomFont ( gbDevice . getAddress ( ) ) ) {
return convertEmojiToCustomFont ( inputString ) ;
}
return inputString ;
}
private String convertEmojiToCustomFont ( String str ) {
StringBuilder sb = new StringBuilder ( ) ;
int i = 0 ;
while ( i < str . length ( ) ) {
char charAt = str . charAt ( i ) ;
if ( Character . isHighSurrogate ( charAt ) ) {
int i2 = i + 1 ;
try {
int codePoint = Character . toCodePoint ( charAt , str . charAt ( i2 ) ) ;
if ( codePoint < 127744 | | codePoint > 129510 ) {
sb . append ( charAt ) ;
} else {
sb . append ( ( char ) ( codePoint - 83712 ) ) ;
i = i2 ;
}
} catch ( StringIndexOutOfBoundsException e ) {
LOG . warn ( " error while converting emoji to custom font " , e ) ;
sb . append ( charAt ) ;
}
} else {
sb . append ( charAt ) ;
}
i + + ;
}
return sb . toString ( ) ;
}
2016-11-13 01:42:55 +01:00
public void phase2Initialize ( TransactionBuilder builder ) {
LOG . info ( " phase2Initialize... " ) ;
2016-12-14 00:50:43 +01:00
requestBatteryInfo ( builder ) ;
2017-09-04 23:19:53 +02:00
}
public void phase3Initialize ( TransactionBuilder builder ) {
2022-10-22 21:53:45 +02:00
final HuamiCoordinator coordinator = getCoordinator ( ) ;
2022-07-05 20:29:16 +02:00
2017-09-04 23:19:53 +02:00
LOG . info ( " phase3Initialize... " ) ;
2022-09-10 15:35:50 +02:00
if ( HuamiCoordinator . getOverwriteSettingsOnConnection ( getDevice ( ) . getAddress ( ) ) ) {
setDateDisplay ( builder ) ;
setTimeFormat ( builder ) ;
setUserInfo ( builder ) ;
setDistanceUnit ( builder ) ;
setWearLocation ( builder ) ;
setFitnessGoal ( builder ) ;
setDisplayItems ( builder ) ;
setDoNotDisturb ( builder ) ;
setRotateWristToSwitchInfo ( builder ) ;
setActivateDisplayOnLiftWrist ( builder ) ;
setDisplayCaller ( builder ) ;
setGoalNotification ( builder ) ;
setInactivityWarnings ( builder ) ;
setHourlyChime ( builder ) ;
setHeartrateSleepSupport ( builder ) ;
setHeartrateActivityMonitoring ( builder ) ;
setHeartrateAlert ( builder ) ;
setHeartrateStressMonitoring ( builder ) ;
setDisconnectNotification ( builder ) ;
setExposeHRThirdParty ( builder ) ;
setHeartrateMeasurementInterval ( builder , HuamiCoordinator . getHeartRateMeasurementInterval ( getDevice ( ) . getAddress ( ) ) ) ;
sendReminders ( builder ) ;
setWorldClocks ( builder ) ;
2022-10-22 21:53:45 +02:00
for ( final HuamiVibrationPatternNotificationType type : coordinator . getVibrationPatternNotificationTypes ( getDevice ( ) ) ) {
2022-09-10 15:35:50 +02:00
final String typeKey = type . name ( ) . toLowerCase ( Locale . ROOT ) ;
setVibrationPattern ( builder , HuamiConst . PREF_HUAMI_VIBRATION_PROFILE_PREFIX + typeKey ) ;
}
if ( ! PasswordCapabilityImpl . Mode . NONE . equals ( coordinator . getPasswordCapability ( ) ) ) {
setPassword ( builder ) ;
}
2022-07-05 20:29:16 +02:00
}
2022-09-10 15:35:50 +02:00
2019-12-27 22:19:17 +01:00
requestAlarms ( builder ) ;
2017-11-11 00:04:51 +01:00
}
2022-06-14 20:15:51 +02:00
public abstract HuamiFWHelper createFWHelper ( Uri uri , Context context ) throws IOException ;
2019-07-25 20:51:28 +02:00
public UpdateFirmwareOperation createUpdateFirmwareOperation ( Uri uri ) {
return new UpdateFirmwareOperation ( uri , this ) ;
}
2019-12-26 23:05:13 +01:00
public int getMTU ( ) {
return mMTU ;
}
2020-07-22 11:03:30 +02:00
2022-10-22 21:53:45 +02:00
protected void setMtu ( final int mtu ) {
final Prefs prefs = getDevicePrefs ( ) ;
2023-06-14 16:44:51 +02:00
if ( ! prefs . getBoolean ( PREF_ALLOW_HIGH_MTU , true ) ) {
2022-10-22 21:53:45 +02:00
LOG . warn ( " High MTU is not allowed, ignoring " ) ;
return ;
}
if ( mtu < 23 ) {
LOG . error ( " Device announced unreasonable low MTU of {}, ignoring " , mtu ) ;
return ;
}
this . mMTU = mtu ;
if ( huami2021ChunkedEncoder ! = null ) {
huami2021ChunkedEncoder . setMTU ( mtu ) ;
}
}
2020-07-22 11:03:30 +02:00
public int getActivitySampleSize ( ) {
return mActivitySampleSize ;
}
2022-08-18 23:03:28 +02:00
2022-10-28 00:50:21 +02:00
public TimeUnit getFetchOperationsTimeUnit ( ) {
2022-11-06 13:13:33 +01:00
// This is configurable because using seconds was causing issues on Amazfit GTR 3
// However, using minutes can cause issues while fetching workouts shorter than 1 minute
final Prefs devicePrefs = getDevicePrefs ( ) ;
final boolean truncate = devicePrefs . getBoolean ( " huami_truncate_fetch_operation_timestamps " , true ) ;
return truncate ? TimeUnit . MINUTES : TimeUnit . SECONDS ;
2022-10-28 00:50:21 +02:00
}
2022-08-18 23:03:28 +02:00
public boolean force2021Protocol ( ) {
return GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) . getBoolean ( " force_new_protocol " , false ) ;
}
2022-10-22 21:53:45 +02:00
protected HuamiCoordinator getCoordinator ( ) {
return ( HuamiCoordinator ) DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
}
protected Prefs getDevicePrefs ( ) {
return new Prefs ( GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ) ;
}
2022-08-18 23:03:28 +02:00
@Override
2022-10-29 13:03:25 +02:00
public void handle2021Payload ( short type , byte [ ] payload ) {
2022-08-18 23:03:28 +02:00
if ( type = = Huami2021Service . CHUNKED2021_ENDPOINT_COMPAT ) {
LOG . info ( " got configuration data " ) ;
type = 0 ;
handleConfigurationInfo ( ArrayUtils . remove ( payload , 0 ) ) ;
return ;
}
2023-05-06 22:02:54 +02:00
if ( type = = ZeppOsCannedMessagesService . ENDPOINT & & false ) { // unsafe for now, disabled
2022-08-18 23:03:28 +02:00
LOG . debug ( " got command for SMS reply " ) ;
if ( payload [ 0 ] = = 0x0d ) {
try {
TransactionBuilder builder = performInitialized ( " allow sms reply " ) ;
2023-05-06 22:02:54 +02:00
writeToChunked2021 ( builder , ZeppOsCannedMessagesService . ENDPOINT , new byte [ ] { ( byte ) ZeppOsCannedMessagesService . CMD_REPLY_SMS_ALLOW , 0x01 } , false ) ;
2022-08-18 23:03:28 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
LOG . error ( " Unable to allow sms reply " ) ;
}
2023-05-06 22:02:54 +02:00
} else if ( payload [ 0 ] = = ZeppOsCannedMessagesService . CMD_REPLY_SMS ) {
2022-08-18 23:03:28 +02:00
String phoneNumber = null ;
String smsReply = null ;
for ( int i = 1 ; i < payload . length ; i + + ) {
if ( payload [ i ] = = 0 ) {
phoneNumber = new String ( payload , 1 , i - 1 ) ;
// there are four unknown bytes between caller and reply
smsReply = new String ( payload , i + 5 , payload . length - i - 6 ) ;
break ;
}
}
if ( phoneNumber ! = null & & ! phoneNumber . isEmpty ( ) ) {
LOG . debug ( " will send message ' " + smsReply + " ' to number ' " + phoneNumber + " ' " ) ;
GBDeviceEventNotificationControl devEvtNotificationControl = new GBDeviceEventNotificationControl ( ) ;
devEvtNotificationControl . handle = - 1 ;
devEvtNotificationControl . phoneNumber = phoneNumber ;
devEvtNotificationControl . reply = smsReply ;
devEvtNotificationControl . event = GBDeviceEventNotificationControl . Event . REPLY ;
evaluateGBDeviceEvent ( devEvtNotificationControl ) ;
try {
TransactionBuilder builder = performInitialized ( " ack sms reply " ) ;
2023-05-06 22:02:54 +02:00
byte [ ] ackSentCommand = new byte [ ] { ZeppOsCannedMessagesService . CMD_REPLY_SMS_ACK , 0x01 } ;
writeToChunked2021 ( builder , ZeppOsCannedMessagesService . ENDPOINT , ackSentCommand , false ) ;
2022-08-18 23:03:28 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
LOG . error ( " Unable to ack sms reply " ) ;
}
}
}
}
}
2022-09-10 01:32:57 +02:00
protected void setRawSensor ( final boolean enable ) {
LOG . info ( " setRawSensor not implemented for HuamiSupport " ) ;
}
protected void handleRawSensorData ( final byte [ ] value ) {
LOG . warn ( " handleRawSensorData not implemented for HuamiSupport " ) ;
}
2016-07-25 00:00:22 +02:00
}