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 ;
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 ;
import java.io.IOException ;
2018-08-06 23:11:40 +02:00
import java.nio.ByteBuffer ;
import java.nio.ByteOrder ;
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 ;
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 ;
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 ;
2017-07-09 16:17:13 +02:00
import java.util.Set ;
2019-08-25 09:55:23 +02:00
import java.util.SimpleTimeZone ;
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 ;
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 ;
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 ;
2019-02-13 13:06:42 +01:00
import nodomain.freeyourgadget.gadgetbridge.devices.huami.DisconnectNotificationSetting ;
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 ;
2017-10-24 22:01:25 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2.MiBand2FWHelper ;
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 ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State ;
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 ;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec ;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents ;
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 ;
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 ;
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 ;
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
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 ;
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 ;
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 ;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION ;
2020-12-18 10:56:42 +01:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_BUTTON_ACTION_SELECTION_BROADCAST ;
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_BROADCAST ;
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 ;
2020-11-07 10:19:28 +01:00
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.DISPLAY_ITEM_BIT_CLOCK ;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY_ITEMS ;
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 ;
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
2018-08-01 22:56:01 +02:00
public class HuamiSupport extends AbstractBTLEDeviceSupport {
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 ;
2020-01-08 19:58:31 +01:00
private Timer buttonActionTimer = 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 ) ) ;
}
}
} ;
2018-07-21 17:18:08 +02:00
private BluetoothGattCharacteristic characteristicHRControlPoint ;
2019-12-26 23:05:13 +01:00
private BluetoothGattCharacteristic characteristicChunked ;
2017-10-23 10:28:54 +02:00
2016-09-20 23:09:42 +02:00
private boolean needsAuth ;
2016-07-25 00:00:22 +02:00
private volatile boolean telephoneRinging ;
private volatile boolean isLocatingDevice ;
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 ;
2017-02-07 23:49:10 +01:00
private boolean alarmClockRinging ;
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 ;
2018-08-06 23:11:40 +02:00
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 ;
2019-07-23 08:56:26 +02:00
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 ;
}
2017-10-19 21:52:38 +02:00
/ * *
* Returns the given date / time ( calendar ) as a byte sequence , suitable for sending to the
2018-07-17 00:29:36 +02:00
* Mi Band 2 ( or derivative ) . The band appears to not handle DST offsets , so we simply add this
2017-10-19 21:52:38 +02:00
* to the timezone .
2020-09-30 18:16:25 +02:00
*
2017-10-19 21:52:38 +02:00
* @param calendar
* @param precision
* @return
* /
2016-12-01 22:49:58 +01:00
public byte [ ] getTimeBytes ( Calendar calendar , TimeUnit precision ) {
byte [ ] bytes ;
if ( precision = = TimeUnit . MINUTES ) {
2019-06-05 14:11:44 +02:00
bytes = BLETypeConversions . shortCalendarToRawBytes ( calendar ) ;
2016-12-01 22:49:58 +01:00
} else if ( precision = = TimeUnit . SECONDS ) {
2019-06-05 14:11:44 +02:00
bytes = BLETypeConversions . calendarToRawBytes ( calendar ) ;
2016-12-01 22:49:58 +01:00
} else {
throw new IllegalArgumentException ( " Unsupported precision, only MINUTES and SECONDS are supported till now " ) ;
}
2017-10-19 21:52:38 +02:00
byte [ ] tail = new byte [ ] { 0 , BLETypeConversions . mapTimeZone ( calendar . getTimeZone ( ) , BLETypeConversions . TZ_FLAG_INCLUDE_DST_IN_TZ ) } ;
// 0 = adjust reason bitflags? or DST offset?? , timezone
2016-08-17 00:53:16 +02:00
// byte[] tail = new byte[] { 0x2 }; // reason
byte [ ] all = BLETypeConversions . join ( bytes , tail ) ;
2016-11-18 21:14:04 +01:00
return all ;
}
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 ) {
2016-11-18 21:14:04 +01:00
GregorianCalendar now = BLETypeConversions . createCalendar ( ) ;
2016-12-01 22:49:58 +01:00
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 ;
}
2018-08-01 22:56:01 +02:00
public HuamiSupport setLowLatency ( TransactionBuilder builder ) {
2017-03-05 21:45:39 +01:00
// TODO: low latency?
2016-07-25 00:00:22 +02:00
return this ;
}
2018-08-01 22:56:01 +02:00
public HuamiSupport setHighLatency ( TransactionBuilder builder ) {
2017-03-05 21:45:39 +01:00
// TODO: high latency?
2016-07-25 00:00:22 +02:00
return this ;
}
/ * *
* 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 ) ;
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 ) ;
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
2018-08-01 22:56:01 +02:00
private 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
* /
2018-08-01 22:56:01 +02:00
private 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
* /
2018-08-01 22:56:01 +02:00
private 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 ( ) ;
String alias = prefs . getString ( MiBandConst . PREF_USER_ALIAS , 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
* /
2018-08-01 22:56:01 +02:00
private 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 ) ;
}
}
2016-07-25 00:00:22 +02:00
@Override
public void onAddCalendarEvent ( CalendarEventSpec calendarEventSpec ) {
// not supported
}
@Override
public void onDeleteCalendarEvent ( byte type , long id ) {
// not supported
}
/ * *
* Part of device initialization process . Do not call manually .
*
* @param builder
* /
2018-08-01 22:56:01 +02:00
private 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 ;
}
2018-08-01 22:56:01 +02:00
private 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 ) ;
}
}
2019-08-29 08:32:29 +02:00
private 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 ) {
try {
2018-08-02 10:55:30 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) ;
2016-07-25 00:00:22 +02:00
TransactionBuilder builder = performInitialized ( " Set alarm " ) ;
boolean anyAlarmEnabled = false ;
for ( Alarm alarm : alarms ) {
2019-01-07 01:10:57 +01:00
anyAlarmEnabled | = alarm . getEnabled ( ) ;
2016-07-25 00:00:22 +02:00
queueAlarm ( alarm , builder , characteristic ) ;
}
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
}
}
/ *
This works on all Huami devices except Mi Band 2
* /
protected void sendNotificationNew ( NotificationSpec notificationSpec , boolean hasExtraHeader ) {
2020-06-15 10:45:39 +02:00
sendNotificationNew ( notificationSpec , hasExtraHeader , 230 ) ;
}
protected void sendNotificationNew ( NotificationSpec notificationSpec , boolean hasExtraHeader , int maxLength ) {
2019-10-31 14:28:24 +01:00
if ( notificationSpec . type = = NotificationType . GENERIC_ALARM_CLOCK ) {
onAlarmClock ( notificationSpec ) ;
return ;
}
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
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 ;
}
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
}
}
@Override
public void onNotification ( NotificationSpec notificationSpec ) {
2017-02-07 23:49:10 +01:00
if ( notificationSpec . type = = NotificationType . GENERIC_ALARM_CLOCK ) {
onAlarmClock ( notificationSpec ) ;
return ;
}
2018-08-02 10:55:30 +02:00
int alertLevel = HuamiService . ALERT_LEVEL_MESSAGE ;
2016-10-11 21:18:43 +02:00
if ( notificationSpec . type = = NotificationType . UNKNOWN ) {
2018-08-02 10:55:30 +02:00
alertLevel = HuamiService . ALERT_LEVEL_VIBRATE_ONLY ;
2016-07-25 00:00:22 +02:00
}
2017-03-02 00:27:54 +01:00
String message = NotificationUtils . getPreferredTextFor ( notificationSpec , 40 , 40 , getContext ( ) ) . trim ( ) ;
2016-10-11 21:18:43 +02:00
String origin = notificationSpec . type . getGenericType ( ) ;
2017-10-22 00:02:36 +02:00
SimpleNotification simpleNotification = new SimpleNotification ( message , BLETypeConversions . toAlertCategory ( notificationSpec . type ) , notificationSpec . type ) ;
2017-03-02 00:27:54 +01:00
performPreferredNotification ( origin + " received " , origin , simpleNotification , alertLevel , null ) ;
2016-07-25 00:00:22 +02:00
}
2017-08-18 16:21:54 +02:00
protected void onAlarmClock ( NotificationSpec notificationSpec ) {
2017-02-07 23:49:10 +01:00
alarmClockRinging = true ;
2019-11-21 13:24:06 +01:00
AbortTransactionAction abortAction = new StopNotificationAction ( getCharacteristic ( UUID_CHARACTERISTIC_ALERT_LEVEL ) ) {
2017-02-07 23:49:10 +01:00
@Override
protected boolean shouldAbort ( ) {
return ! isAlarmClockRinging ( ) ;
}
} ;
2017-03-02 00:27:54 +01:00
String message = NotificationUtils . getPreferredTextFor ( notificationSpec , 40 , 40 , getContext ( ) ) ;
2017-10-22 00:02:36 +02:00
SimpleNotification simpleNotification = new SimpleNotification ( message , AlertCategory . HighPriorityAlert , notificationSpec . type ) ;
2018-08-02 10:55:30 +02:00
performPreferredNotification ( " alarm clock ringing " , MiBandConst . ORIGIN_ALARM_CLOCK , simpleNotification , HuamiService . ALERT_LEVEL_VIBRATE_ONLY , abortAction ) ;
2017-02-07 23:49:10 +01:00
}
2017-01-09 16:33:00 +01:00
@Override
public void onDeleteNotification ( int id ) {
2017-02-07 23:49:10 +01:00
alarmClockRinging = false ; // we should have the notificationtype at least to check
2017-01-09 16:33:00 +01:00
}
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
2020-02-24 14:19:06 +01:00
if ( characteristicChunked = = null ) { // all except Mi Band 2
2020-02-19 09:41:50 +01:00
sendCalendarEvents ( builder ) ;
}
else {
sendCalendarEventsAsReminder ( 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 ) {
}
2017-02-07 23:49:10 +01:00
private boolean isAlarmClockRinging ( ) {
// don't synchronize, this is not really important
return alarmClockRinging ;
}
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
}
2020-06-22 18:06:40 +02:00
private void sendMusicStateToDevice ( ) {
sendMusicStateToDevice ( bufferMusicSpec , bufferMusicStateSpec ) ;
}
2020-06-22 18:35:22 +02:00
protected void sendMusicStateToDevice ( MusicSpec musicSpec , 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 ;
}
byte flags = 0x00 ;
flags | = 0x01 ;
2020-06-22 18:06:40 +02:00
int length = 5 ;
if ( musicSpec ! = null ) {
if ( musicSpec . artist ! = null & & musicSpec . artist . getBytes ( ) . length > 0 ) {
length + = musicSpec . artist . getBytes ( ) . length + 1 ;
flags | = 0x02 ;
}
if ( musicSpec . album ! = null & & musicSpec . album . getBytes ( ) . length > 0 ) {
length + = musicSpec . album . getBytes ( ) . length + 1 ;
flags | = 0x04 ;
}
if ( musicSpec . track ! = null & & musicSpec . track . getBytes ( ) . length > 0 ) {
length + = musicSpec . track . getBytes ( ) . length + 1 ;
flags | = 0x08 ;
}
if ( musicSpec . duration ! = 0 ) {
length + = 2 ;
flags | = 0x10 ;
}
2018-08-06 23:11:40 +02:00
}
try {
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( flags ) ;
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 ) ;
2018-08-06 23:11:40 +02:00
2020-06-22 18:06:40 +02:00
if ( musicSpec ! = null ) {
if ( musicSpec . artist ! = null & & musicSpec . artist . getBytes ( ) . length > 0 ) {
buf . put ( musicSpec . artist . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
}
if ( musicSpec . album ! = null & & musicSpec . album . getBytes ( ) . length > 0 ) {
buf . put ( musicSpec . album . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
}
if ( musicSpec . track ! = null & & musicSpec . track . getBytes ( ) . length > 0 ) {
buf . put ( musicSpec . track . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
}
if ( musicSpec . duration ! = 0 ) {
buf . putShort ( ( short ) musicSpec . duration ) ;
}
2018-08-06 23:11:40 +02:00
}
TransactionBuilder builder = performInitialized ( " send playback info " ) ;
writeToChunked ( builder , 3 , buf . array ( ) ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
LOG . error ( " Unable to send playback state " ) ;
}
2020-06-22 18:06:40 +02:00
LOG . info ( " sendMusicStateToDevice: " + musicSpec + " " + musicStateSpec ) ;
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 ) {
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_FACTORY_RESET ) ;
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
}
2018-11-05 23:27:29 +01:00
private void enableNotifyHeartRateMeasurements ( boolean enable , TransactionBuilder builder ) {
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 ) {
isLocatingDevice = start ;
if ( start ) {
AbortTransactionAction abortAction = new AbortTransactionAction ( ) {
@Override
protected boolean shouldAbort ( ) {
return ! isLocatingDevice ;
}
} ;
2017-10-22 00:02:36 +02:00
SimpleNotification simpleNotification = new SimpleNotification ( getContext ( ) . getString ( R . string . find_device_you_found_it ) , AlertCategory . HighPriorityAlert , null ) ;
2017-03-02 00:27:54 +01:00
performDefaultNotification ( " locating device " , simpleNotification , ( short ) 255 , abortAction ) ;
2016-07-25 00:00:22 +02:00
}
}
2016-09-20 20:28:52 +02:00
@Override
public void onSetConstantVibration ( int intensity ) {
}
2016-07-25 00:00:22 +02:00
@Override
2018-03-31 16:21:25 +02:00
public void onFetchRecordedData ( int dataTypes ) {
2016-11-18 21:14:04 +01:00
try {
new FetchActivityOperation ( this ) . perform ( ) ;
} catch ( IOException ex ) {
2019-08-02 00:11:11 +02:00
LOG . error ( " Unable to fetch activity data " , ex ) ;
2016-11-18 21:14:04 +01:00
}
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
}
private byte [ ] getHighLatency ( ) {
int minConnectionInterval = 460 ;
int maxConnectionInterval = 500 ;
int latency = 0 ;
int timeout = 500 ;
int advertisementInterval = 0 ;
return getLatency ( minConnectionInterval , maxConnectionInterval , latency , timeout , advertisementInterval ) ;
}
private byte [ ] getLatency ( int minConnectionInterval , int maxConnectionInterval , int latency , int timeout , int advertisementInterval ) {
2019-09-14 00:05:39 +02:00
byte [ ] result = new byte [ 12 ] ;
2016-07-25 00:00:22 +02:00
result [ 0 ] = ( byte ) ( minConnectionInterval & 0xff ) ;
result [ 1 ] = ( byte ) ( 0xff & minConnectionInterval > > 8 ) ;
result [ 2 ] = ( byte ) ( maxConnectionInterval & 0xff ) ;
result [ 3 ] = ( byte ) ( 0xff & maxConnectionInterval > > 8 ) ;
result [ 4 ] = ( byte ) ( latency & 0xff ) ;
result [ 5 ] = ( byte ) ( 0xff & latency > > 8 ) ;
result [ 6 ] = ( byte ) ( timeout & 0xff ) ;
result [ 7 ] = ( byte ) ( 0xff & timeout > > 8 ) ;
result [ 8 ] = 0 ;
result [ 9 ] = 0 ;
result [ 10 ] = ( byte ) ( advertisementInterval & 0xff ) ;
result [ 11 ] = ( byte ) ( 0xff & advertisementInterval > > 8 ) ;
return result ;
}
private byte [ ] getLowLatency ( ) {
int minConnectionInterval = 39 ;
int maxConnectionInterval = 49 ;
int latency = 0 ;
int timeout = 500 ;
int advertisementInterval = 0 ;
return getLatency ( minConnectionInterval , maxConnectionInterval , latency , timeout , advertisementInterval ) ;
}
@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
}
@Override
public void onAppInfoReq ( ) {
// not supported
}
@Override
public void onAppStart ( UUID uuid , boolean start ) {
// not supported
}
@Override
public void onAppDelete ( UUID uuid ) {
// not supported
}
@Override
2017-08-01 00:03:28 +02:00
public void onAppConfiguration ( UUID uuid , String config , Integer id ) {
2016-07-25 00:00:22 +02:00
// not supported
}
@Override
public void onAppReorder ( UUID [ ] uuids ) {
// not supported
}
@Override
public void onScreenshotReq ( ) {
// not supported
}
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
}
2020-12-18 10:56:42 +01:00
if ( buttonPreference . equals ( PREF_BUTTON_ACTION_SELECTION_BROADCAST ) ) {
sendSystemBroadcastWithButtonId ( ) ;
} else {
handleMediaButton ( buttonPreference ) ;
}
2017-09-10 21:11:50 +02:00
}
2020-08-16 22:07:55 +02:00
private void handleDeviceAction ( String deviceAction , String message ) {
if ( deviceAction . equals ( PREF_DEVICE_ACTION_SELECTION_OFF ) ) {
return ;
}
if ( deviceAction . equals ( PREF_DEVICE_ACTION_SELECTION_BROADCAST ) ) {
sendSystemBroadcast ( message ) ;
} else {
handleMediaButton ( deviceAction ) ;
}
}
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 :
LOG . info ( " An alarm was toggled " ) ;
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 :
LOG . info ( " Music app started " ) ;
isMusicAppStarted = true ;
sendMusicStateToDevice ( ) ;
break ;
case ( byte ) 225 :
LOG . info ( " Music app terminated " ) ;
isMusicAppStarted = false ;
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 ) ;
2020-02-04 10:04:01 +01:00
Prefs prefs = new Prefs ( GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ) ;
if ( ! prefs . getBoolean ( PREF_ALLOW_HIGH_MTU , false ) ) {
break ;
}
2020-08-02 13:11:41 +02:00
if ( mtu < 23 ) {
LOG . error ( " Device announced unreasonable low MTU of " + mtu + " , ignoring " ) ;
break ;
}
2019-12-26 23:05:13 +01:00
mMTU = mtu ;
/ *
* 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 ) ;
}
* /
break ;
2017-10-21 22:50:28 +02:00
default :
LOG . warn ( " unhandled event " + value [ 0 ] ) ;
}
}
2019-12-26 23:05:13 +01:00
private void requestMTU ( int mtu ) {
if ( GBApplication . isRunningLollipopOrLater ( ) ) {
new TransactionBuilder ( " requestMtu " )
. requestMtu ( mtu )
. queue ( getQueue ( ) ) ;
mMTU = mtu ;
}
}
2018-01-13 18:46:21 +01:00
private void acknowledgeFindPhone ( ) {
try {
TransactionBuilder builder = performInitialized ( " acknowledge find phone " ) ;
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , AmazfitBipService . COMMAND_ACK_FIND_PHONE_IN_PROGRESS ) ;
2018-01-13 18:46:21 +01:00
builder . queue ( getQueue ( ) ) ;
} catch ( Exception ex ) {
LOG . error ( " Error sending current weather " , ex ) ;
}
}
2020-08-16 22:07:55 +02:00
private void processDeviceEvent ( int event ) {
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 ;
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 ;
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 ;
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 ) ;
}
private 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
}
}
private 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
}
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 ) {
if ( value [ 1 ] = = 0x0e ) {
String gpsVersion = new String ( value , 3 , value . length - 3 ) ;
LOG . info ( " got gps version = " + gpsVersion ) ;
gbDevice . setFirmwareVersion2 ( gpsVersion ) ;
} else if ( value [ 1 ] = = 0x0d ) {
LOG . info ( " got alarms from watch " ) ;
decodeAndUpdateAlarmStatus ( value ) ;
} else {
LOG . warn ( " got configuration info we do not handle yet " + GB . hexdump ( value , 3 , - 1 ) ) ;
}
} else {
LOG . warn ( " error received from configuration request " + GB . hexdump ( value , 0 , - 1 ) ) ;
}
}
private void decodeAndUpdateAlarmStatus ( byte [ ] response ) {
List < nodomain . freeyourgadget . gadgetbridge . entities . Alarm > alarms = DBHelper . getAlarms ( gbDevice ) ;
2020-02-26 22:25:03 +01:00
int maxAlarms = 10 ;
boolean [ ] alarmsInUse = new boolean [ maxAlarms ] ;
boolean [ ] alarmsEnabled = new boolean [ maxAlarms ] ;
2019-12-27 22:19:17 +01:00
int nr_alarms = response [ 8 ] ;
for ( int i = 0 ; i < nr_alarms ; i + + ) {
byte alarm_data = response [ 9 + i ] ;
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 ;
boolean enabled = ( alarm_data & 0x10 ) = = 0x10 ;
alarmsEnabled [ index ] = enabled ;
LOG . info ( " alarm " + index + " is enabled: " + enabled ) ;
}
for ( nodomain . freeyourgadget . gadgetbridge . entities . Alarm alarm : alarms ) {
boolean enabled = alarmsEnabled [ alarm . getPosition ( ) ] ;
boolean unused = ! alarmsInUse [ alarm . getPosition ( ) ] ;
if ( alarm . getEnabled ( ) ! = enabled | | alarm . getUnused ( ) ! = unused ) {
LOG . info ( " updating alarm index " + alarm . getPosition ( ) + " unused= " + unused + " , enabled= " + enabled ) ;
alarm . setEnabled ( enabled ) ;
alarm . setUnused ( unused ) ;
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
}
}
}
2016-11-29 23:22:36 +01:00
private void enableRealtimeSamplesTimer ( boolean enable ) {
if ( enable ) {
getRealtimeSamplesSupport ( ) . start ( ) ;
} else {
if ( realtimeSamplesSupport ! = null ) {
realtimeSamplesSupport . stop ( ) ;
}
}
}
public MiBandActivitySample createActivitySample ( Device device , User user , int timestampInSeconds , SampleProvider provider ) {
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
* @param characteristic
* /
private void queueAlarm ( Alarm alarm , TransactionBuilder builder , BluetoothGattCharacteristic characteristic ) {
2019-01-07 01:10:57 +01:00
Calendar calendar = AlarmUtils . toCalendar ( alarm ) ;
2016-10-21 00:49:42 +02:00
2018-11-24 12:16:47 +01:00
DeviceCoordinator coordinator = DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
int maxAlarms = coordinator . getAlarmSlotCount ( ) ;
2019-01-07 01:10:57 +01:00
if ( alarm . getPosition ( ) > = maxAlarms ) {
if ( alarm . getEnabled ( ) ) {
2018-11-24 12:16:47 +01:00
GB . toast ( getContext ( ) , " Only " + maxAlarms + " alarms are currently supported. " , Toast . LENGTH_LONG , GB . WARN ) ;
2016-10-23 22:37:18 +02:00
}
2016-10-21 01:01:30 +02:00
return ;
}
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
2016-10-21 00:49:42 +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
} ;
2016-10-21 01:01:30 +02:00
builder . write ( characteristic , alarmMessage ) ;
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 ) ;
2016-07-25 00:00:22 +02:00
batteryCmd . level = ( ( short ) info . getLevelInPercent ( ) ) ;
batteryCmd . state = info . getState ( ) ;
batteryCmd . lastChargeTime = info . getLastChargeTime ( ) ;
batteryCmd . numCharges = info . getNumCharges ( ) ;
handleGBDeviceEvent ( batteryCmd ) ;
}
}
/ * *
* 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
* /
2018-08-01 22:56:01 +02:00
private HuamiSupport sendCalendarEvents ( TransactionBuilder builder ) {
2018-08-02 10:55:30 +02:00
BluetoothGattCharacteristic characteristic = getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) ;
2016-07-25 00:00:22 +02:00
2019-12-14 23:43:54 +01:00
Prefs prefs = new Prefs ( GBApplication . getDeviceSpecificSharedPrefs ( gbDevice . getAddress ( ) ) ) ;
2020-02-04 10:04:01 +01:00
int availableSlots = prefs . getInt ( PREF_RESERVER_ALARMS_CALENDAR , 0 ) ;
2016-07-25 00:00:22 +02:00
2016-12-01 22:49:58 +01:00
if ( availableSlots > 0 ) {
CalendarEvents upcomingEvents = new CalendarEvents ( ) ;
List < CalendarEvents . CalendarEvent > mEvents = upcomingEvents . getCalendarEventList ( getContext ( ) ) ;
2016-07-25 00:00:22 +02:00
2016-12-01 22:49:58 +01:00
int iteration = 0 ;
2018-11-24 12:16:47 +01:00
2016-12-01 22:49:58 +01:00
for ( CalendarEvents . CalendarEvent mEvt : mEvents ) {
2020-03-20 17:21:20 +01:00
if ( mEvt . isAllDay ( ) ) {
continue ;
}
2016-12-01 22:49:58 +01:00
if ( iteration > = availableSlots | | iteration > 2 ) {
break ;
2016-07-25 00:00:22 +02:00
}
2016-12-01 22:49:58 +01:00
int slotToUse = 2 - iteration ;
Calendar calendar = Calendar . getInstance ( ) ;
calendar . setTimeInMillis ( mEvt . getBegin ( ) ) ;
2020-01-12 08:12:21 +01:00
Alarm alarm = AlarmUtils . createSingleShot ( slotToUse , false , true , calendar ) ;
2016-12-01 22:49:58 +01:00
queueAlarm ( alarm , builder , characteristic ) ;
iteration + + ;
2016-07-25 00:00:22 +02:00
}
}
2016-12-01 22:49:58 +01:00
return this ;
2016-07-25 00:00:22 +02:00
}
2020-02-18 22:20:57 +01:00
private HuamiSupport sendCalendarEventsAsReminder ( 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 ;
}
2020-02-18 22:20:57 +01:00
CalendarEvents upcomingEvents = new CalendarEvents ( ) ;
List < CalendarEvents . CalendarEvent > calendarEvents = upcomingEvents . getCalendarEventList ( getContext ( ) ) ;
Calendar calendar = Calendar . getInstance ( ) ;
int iteration = 0 ;
for ( CalendarEvents . CalendarEvent calendarEvent : calendarEvents ) {
2020-03-20 17:21:20 +01:00
if ( calendarEvent . isAllDay ( ) ) {
continue ;
}
2020-02-18 22:20:57 +01:00
if ( iteration > 8 ) { // limit ?
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 ;
byte [ ] body ;
if ( calendarEvent . getTitle ( ) ! = null ) {
title = calendarEvent . getTitle ( ) . getBytes ( ) ;
} else {
title = new byte [ ] { } ;
}
if ( calendarEvent . getDescription ( ) ! = null ) {
body = calendarEvent . getDescription ( ) . getBytes ( ) ;
} else {
body = new byte [ ] { } ;
}
2020-02-18 22:20:57 +01:00
int length = 18 + title . length + 1 + body . length + 1 ;
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( ( byte ) 0x0b ) ; // always 0x0b?
2020-02-19 09:41:50 +01:00
buf . put ( ( byte ) iteration ) ; // îd
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 ( title ) ;
buf . put ( ( byte ) 0 ) ; // 0 Terminated
buf . put ( body ) ;
buf . put ( ( byte ) 0 ) ; // 0 Terminated
writeToChunked ( builder , 2 , buf . array ( ) ) ;
iteration + + ;
}
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 ;
2017-07-15 00:40:58 +02:00
case MiBandConst . PREF_MI2_GOAL_NOTIFICATION :
setGoalNotification ( builder ) ;
break ;
2019-02-13 13:06:42 +01:00
case HuamiConst . PREF_ACTIVATE_DISPLAY_ON_LIFT :
case HuamiConst . PREF_DISPLAY_ON_LIFT_START :
case HuamiConst . PREF_DISPLAY_ON_LIFT_END :
2016-11-13 21:33:43 +01:00
setActivateDisplayOnLiftWrist ( builder ) ;
break ;
2019-02-13 13:06:42 +01:00
case HuamiConst . PREF_DISCONNECT_NOTIFICATION :
case HuamiConst . PREF_DISCONNECT_NOTIFICATION_START :
case HuamiConst . PREF_DISCONNECT_NOTIFICATION_END :
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 ;
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 :
2016-11-24 21:58:32 +01:00
setFitnessGoal ( builder ) ;
break ;
2019-05-24 23:46:20 +02:00
case MiBandConst . PREF_DO_NOT_DISTURB :
case MiBandConst . PREF_DO_NOT_DISTURB_START :
case MiBandConst . PREF_DO_NOT_DISTURB_END :
2017-07-15 15:01:07 +02:00
setDoNotDisturb ( builder ) ;
2017-07-15 22:48:26 +02:00
break ;
case MiBandConst . PREF_MI2_INACTIVITY_WARNINGS :
case MiBandConst . PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD :
case MiBandConst . PREF_MI2_INACTIVITY_WARNINGS_START :
case MiBandConst . PREF_MI2_INACTIVITY_WARNINGS_END :
case MiBandConst . PREF_MI2_INACTIVITY_WARNINGS_DND :
case MiBandConst . PREF_MI2_INACTIVITY_WARNINGS_DND_START :
case MiBandConst . PREF_MI2_INACTIVITY_WARNINGS_DND_END :
setInactivityWarnings ( 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 :
setExposeHRThridParty ( builder ) ;
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 ;
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 ) ;
}
}
2019-02-20 14:09:21 +01:00
@Override
public void onReadConfiguration ( String config ) {
}
2016-10-11 23:06:59 +02:00
@Override
public void onTestNewFunction ( ) {
2021-02-07 16:23:45 +01:00
try {
boolean test = false ;
TransactionBuilder builder = performInitialized ( " test pattern " ) ;
byte [ ] testpattern = new byte [ ] { 0x20 , 0x00 , ( byte ) 0x4b , 0x64 , 0x00 , ( byte ) 0x8d , 0x01 , 0x73 , 0x00 , 0x38 , 0x01 , 0x64 , 0x00 , 0x64 , 0x00 , 0x64 , 0x00 , 0x67 , 0x00 , 0x64 , 0x00 , 0x37 , 0x01 , 0x7c , 0x00 , 0x64 , 0x00 , 0x64 , 0x00 , 0x67 , 0x00 , 0x64 , 0x00 , 0x67 , 0x00 , 0x64 , 0x00 , 0x37 , 0x01 , 0x64 , 0x00 , 0x64 , 0x00 , 0x64 , 0x00 , ( byte ) 0xe5 , 0x02 } ;
//byte[] testpattern = new byte[] {0x20,0x00, (byte) 0x00, 0,0,0,0};
writeToChunked ( builder , 2 , testpattern ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( Exception ignored ) { }
2016-10-11 23:06:59 +02:00
}
2016-11-13 01:42:55 +01:00
2016-12-31 15:56:05 +01:00
@Override
public void onSendWeather ( WeatherSpec weatherSpec ) {
2019-08-25 09:55:23 +02:00
// FIXME: currently HuamiSupport *is* MiBand2 support, so return if we are using Mi Band 2
if ( gbDevice . getType ( ) = = DeviceType . MIBAND2 ) {
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 " ) ;
String windString = weatherSpec . windSpeed + " km/h " ;
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 ) ;
}
if ( GBApplication . getPrefs ( ) . getBoolean ( " send_sunrise_sunset " , false ) ) {
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
}
2018-08-01 22:56:01 +02:00
private 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 :
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . DATEFORMAT_TIME ) ;
2016-11-13 01:42:55 +01:00
break ;
case DATE_TIME :
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , 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 ) {
return null ;
}
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 ) ;
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , command ) ;
break ;
default :
LOG . warn ( " unsupported date format " + dateFormat ) ;
}
return this ;
}
2018-08-01 22:56:01 +02:00
private 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 ) ;
if ( timeFormat . equals ( " 24h " ) ) {
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . DATEFORMAT_TIME_24_HOURS ) ;
2017-03-03 22:32:54 +01:00
} else {
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . DATEFORMAT_TIME_12_HOURS ) ;
2017-03-03 22:14:28 +01:00
}
return this ;
}
2018-08-01 22:56:01 +02:00
private HuamiSupport setGoalNotification ( TransactionBuilder builder ) {
2017-10-23 14:46:08 +02:00
boolean enable = HuamiCoordinator . getGoalNotification ( ) ;
2017-07-15 00:40:58 +02:00
LOG . info ( " Setting goal notification to " + enable ) ;
if ( enable ) {
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_ENABLE_GOAL_NOTIFICATION ) ;
2017-07-15 00:40:58 +02:00
} else {
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_DISABLE_GOAL_NOTIFICATION ) ;
2017-07-15 00:40:58 +02:00
}
return this ;
}
2018-08-01 22:56:01 +02:00
private 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 :
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST ) ;
2018-03-23 23:27:03 +01:00
break ;
case OFF :
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , 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 ) ;
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , cmd ) ;
2016-11-13 21:33:43 +01:00
}
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
}
2020-05-12 22:42:19 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , 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
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , command ) ;
return this ;
}
2020-11-10 21:56:00 +01:00
protected HuamiSupport setDisplayItemsNew ( TransactionBuilder builder , boolean isShortcuts , 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 ;
List < 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 ) {
enabledList = Arrays . asList ( getContext ( ) . getResources ( ) . getStringArray ( defaultSettings ) ) ;
} else {
enabledList = Arrays . asList ( pages . split ( " , " ) ) ;
2020-10-21 21:43:48 +02:00
}
2020-11-06 21:47:54 +01:00
LOG . info ( " enabled items " + enabledList ) ;
2020-10-20 20:12:08 +02:00
2020-11-10 21:56:00 +01:00
byte [ ] command = new byte [ ( enabledList . size ( ) + 1 ) * 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
command [ pos + + ] = ( byte ) index + + ;
command [ pos + + ] = 0x00 ;
command [ pos + + ] = menuType ;
command [ pos + + ] = 0x12 ;
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
}
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 ;
}
2018-08-01 22:56:01 +02:00
private 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 ) {
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_ENABLE_ROTATE_WRIST_TO_SWITCH_INFO ) ;
2017-07-09 14:30:03 +02:00
} else {
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , 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 ;
}
2018-08-01 22:56:01 +02:00
private HuamiSupport setDisplayCaller ( TransactionBuilder builder ) {
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_ENABLE_DISPLAY_CALLER ) ;
2017-08-25 01:04:36 +02:00
return this ;
}
2018-08-01 22:56:01 +02:00
private HuamiSupport setDoNotDisturb ( TransactionBuilder builder ) {
2019-05-24 23:46:20 +02:00
DoNotDisturb doNotDisturb = HuamiCoordinator . getDoNotDisturb ( gbDevice . getAddress ( ) ) ;
2017-07-15 15:01:07 +02:00
LOG . info ( " Setting do not disturb to " + doNotDisturb ) ;
switch ( doNotDisturb ) {
case OFF :
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_DO_NOT_DISTURB_OFF ) ;
2017-07-15 15:01:07 +02:00
break ;
case AUTOMATIC :
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_DO_NOT_DISTURB_AUTOMATIC ) ;
2017-07-15 15:01:07 +02:00
break ;
case SCHEDULED :
2018-08-02 10:55:30 +02:00
byte [ ] 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
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , data ) ;
2017-07-15 15:01:07 +02:00
break ;
}
return this ;
}
2018-08-01 22:56:01 +02:00
private HuamiSupport setInactivityWarnings ( TransactionBuilder builder ) {
2017-10-23 14:46:08 +02:00
boolean enable = HuamiCoordinator . getInactivityWarnings ( ) ;
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
2017-10-23 14:46:08 +02:00
int threshold = HuamiCoordinator . getInactivityWarningsThreshold ( ) ;
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 ( ) ;
2017-10-23 14:46:08 +02:00
boolean enableDnd = HuamiCoordinator . getInactivityWarningsDnd ( ) ;
2017-07-15 22:48:26 +02:00
2017-10-23 14:46:08 +02:00
Date intervalStart = HuamiCoordinator . getInactivityWarningsStart ( ) ;
Date intervalEnd = HuamiCoordinator . getInactivityWarningsEnd ( ) ;
Date dndStart = HuamiCoordinator . getInactivityWarningsDndStart ( ) ;
Date dndEnd = HuamiCoordinator . getInactivityWarningsDndEnd ( ) ;
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
}
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , data ) ;
2017-07-15 22:48:26 +02:00
} else {
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_DISABLE_INACTIVITY_WARNINGS ) ;
2017-07-15 22:48:26 +02:00
}
return this ;
}
2019-02-13 13:06:42 +01:00
private 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 :
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_ENABLE_DISCONNECT_NOTIFCATION ) ;
break ;
case OFF :
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_DISABLE_DISCONNECT_NOTIFCATION ) ;
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 ) ;
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , cmd ) ;
}
return this ;
}
2018-08-01 22:56:01 +02:00
private 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 ) {
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_DISTANCE_UNIT_METRIC ) ;
2017-10-02 22:23:17 +02:00
} else {
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , 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 ) {
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , MiBand3Service . COMMAND_ENABLE_BAND_SCREEN_UNLOCK ) ;
} else {
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , MiBand3Service . COMMAND_DISABLE_BAND_SCREEN_UNLOCK ) ;
}
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 ;
}
final byte [ ] finalCommand_old = command_old ;
builder . add ( new ConditionalWriteAction ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) ) {
@Override
protected byte [ ] checkCondition ( ) {
2019-05-20 23:27:17 +02:00
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 ) ) {
2019-05-20 16:36:06 +02:00
return finalCommand_old ;
2019-05-20 23:27:17 +02:00
} else {
return command_new ;
2019-05-20 16:36:06 +02:00
}
}
} ) ;
return this ;
}
2019-08-27 11:13:45 +02:00
private HuamiSupport setExposeHRThridParty ( TransactionBuilder builder ) {
boolean enable = HuamiCoordinator . getExposeHRThirdParty ( gbDevice . getAddress ( ) ) ;
LOG . info ( " Setting exposure of HR to third party apps to: " + enable ) ;
if ( enable ) {
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_ENBALE_HR_CONNECTION ) ;
} else {
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_DISABLE_HR_CONNECTION ) ;
}
return this ;
}
2020-12-06 00:09:19 +01:00
private HuamiSupport setBtConnectedAdvertising ( TransactionBuilder builder ) {
boolean enable = HuamiCoordinator . getBtConnectedAdvertising ( gbDevice . getAddress ( ) ) ;
LOG . info ( " Setting connected advertisement to: " + enable ) ;
if ( enable ) {
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_ENABLE_BT_CONNECTED_ADVERTISEMENT ) ;
} else {
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_DISABLE_BT_CONNECTED_ADVERTISEMENT ) ;
}
return this ;
}
2020-04-27 22:55:17 +02:00
protected void writeToChunked ( 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 ;
}
}
2019-12-27 22:19:17 +01:00
protected HuamiSupport requestGPSVersion ( TransactionBuilder builder ) {
LOG . info ( " Requesting GPS version " ) ;
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_REQUEST_GPS_VERSION ) ;
return this ;
}
private HuamiSupport requestAlarms ( TransactionBuilder builder ) {
LOG . info ( " Requesting alarms " ) ;
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , HuamiService . COMMAND_REQUEST_ALARMS ) ;
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 ) {
LOG . info ( " phase3Initialize... " ) ;
2016-11-13 20:47:24 +01:00
setDateDisplay ( builder ) ;
2017-03-03 22:14:28 +01:00
setTimeFormat ( builder ) ;
2017-09-23 00:51:57 +02:00
setUserInfo ( builder ) ;
2017-10-02 22:23:17 +02:00
setDistanceUnit ( builder ) ;
2016-11-13 01:42:55 +01:00
setWearLocation ( builder ) ;
2016-11-24 21:58:32 +01:00
setFitnessGoal ( builder ) ;
2017-07-09 16:17:13 +02:00
setDisplayItems ( builder ) ;
2017-07-15 15:01:07 +02:00
setDoNotDisturb ( builder ) ;
2017-07-09 14:30:03 +02:00
setRotateWristToSwitchInfo ( builder ) ;
2016-11-13 21:33:43 +01:00
setActivateDisplayOnLiftWrist ( builder ) ;
2017-08-25 01:04:36 +02:00
setDisplayCaller ( builder ) ;
2017-07-15 21:15:42 +02:00
setGoalNotification ( builder ) ;
2017-07-15 22:48:26 +02:00
setInactivityWarnings ( builder ) ;
2016-11-13 01:42:55 +01:00
setHeartrateSleepSupport ( builder ) ;
2019-02-13 13:06:42 +01:00
setDisconnectNotification ( builder ) ;
2019-08-27 11:13:45 +02:00
setExposeHRThridParty ( builder ) ;
2017-11-11 00:04:51 +01:00
setHeartrateMeasurementInterval ( builder , getHeartRateMeasurementInterval ( ) ) ;
2019-12-27 22:19:17 +01:00
requestAlarms ( builder ) ;
2017-11-11 00:04:51 +01:00
}
private int getHeartRateMeasurementInterval ( ) {
return GBApplication . getPrefs ( ) . getInt ( " heartrate_measurement_interval " , 0 ) / 60 ;
2016-11-13 01:42:55 +01:00
}
2017-10-24 22:01:25 +02:00
public HuamiFWHelper createFWHelper ( Uri uri , Context context ) throws IOException {
return new MiBand2FWHelper ( uri , context ) ;
}
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
public int getActivitySampleSize ( ) {
return mActivitySampleSize ;
}
2016-07-25 00:00:22 +02:00
}