2018-02-26 14:27:32 +01:00
/ * Copyright ( C ) 2015 - 2018 Andreas Shimokawa , Carsten Pfeiffer , Christian
2017-08-25 11:25:01 +02:00
Fischer , Daniele Gobbetti , JohnnySun , José Rebelo , Julien Pivotto , Kasha ,
2017-09-12 12:39:15 +02:00
Michal Novotny , Sergey Trofimov , Steffen Liebergeld
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.BroadcastReceiver ;
import android.content.Context ;
import android.content.Intent ;
import android.content.IntentFilter ;
import android.net.Uri ;
import android.support.v4.content.LocalBroadcastManager ;
2017-03-03 22:32:54 +01:00
import android.text.format.DateFormat ;
2016-07-25 00:00:22 +02:00
import android.widget.Toast ;
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 ;
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 ;
import java.util.List ;
2017-07-09 16:17:13 +02:00
import java.util.Set ;
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
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 ;
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 ;
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 ;
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-10-20 20:08:17 +02:00
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm ;
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 ;
2018-07-17 00:29:36 +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 ;
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 ;
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 ;
2016-07-25 00:00:22 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile ;
2016-10-23 23:53:48 +02:00
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.heartrate.HeartRateProfile ;
2017-03-02 00:27:54 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification ;
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.actions.StopNotificationAction ;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchActivityOperation ;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSportsSummaryOperation ;
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-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 ;
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 ;
2017-03-15 00:26:39 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.Version ;
2016-07-25 00:00:22 +02:00
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_COLOUR ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_DURATION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_ORIGINAL_COLOUR ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_DURATION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PAUSE ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_VIBRATION_PROFILE ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_COLOUR ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_DURATION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_ORIGINAL_COLOUR ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_COUNT ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_DURATION ;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_PAUSE ;
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 ;
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 ;
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 ;
private final HeartRateProfile < HuamiSupport > heartRateProfile ;
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 ;
protected 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
2018-08-06 23:11:40 +02:00
private boolean isMusicAppStarted = false ;
private MusicSpec bufferMusicSpec = null ;
private MusicStateSpec bufferMusicStateSpec = null ;
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 ) ;
2016-11-29 23:22:36 +01:00
heartRateProfile = new HeartRateProfile < > ( this ) ;
2016-10-24 00:23:59 +02:00
addSupportedProfile ( heartRateProfile ) ;
2016-07-25 00:00:22 +02:00
}
@Override
protected TransactionBuilder initializeDevice ( TransactionBuilder builder ) {
2016-09-20 23:09:42 +02:00
try {
boolean authenticate = needsAuth ;
needsAuth = false ;
2018-08-02 10:55:30 +02:00
byte authFlags = HuamiService . AUTH_BYTE ;
2018-07-17 00:29:36 +02:00
if ( gbDevice . getType ( ) = = DeviceType . MIBAND3 ) {
authFlags = 0x00 ;
}
new InitOperation ( authenticate , authFlags , 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 ) {
GB . toast ( getContext ( ) , " Initializing Mi Band 2 failed " , Toast . LENGTH_SHORT , GB . ERROR , e ) ;
}
2016-07-25 00:00:22 +02:00
return builder ;
}
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 .
* @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 ) {
bytes = BLETypeConversions . shortCalendarToRawBytes ( calendar , true ) ;
} else if ( precision = = TimeUnit . SECONDS ) {
bytes = BLETypeConversions . calendarToRawBytes ( calendar , true ) ;
} 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 ) {
GregorianCalendar timestamp = BLETypeConversions . rawBytesToCalendar ( bytes , true ) ;
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 ) {
2016-07-25 00:00:22 +02:00
builder . add ( new SetDeviceStateAction ( getDevice ( ) , State . INITIALIZED , getContext ( ) ) ) ;
}
// 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 ) ;
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 ) ;
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 ;
2017-04-12 21:33:19 +02:00
return super . 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 ;
}
/ * *
* Adds a custom notification to the given transaction builder
* @param vibrationProfile specifies how and how often the Band shall vibrate .
2017-03-02 00:27:54 +01:00
* @param simpleNotification
2016-07-25 00:00:22 +02:00
* @param flashTimes
* @param flashColour
* @param originalColour
* @param flashDuration
* @param extraAction an extra action to be executed after every vibration and flash sequence . Allows to abort the repetition , for example .
* @param builder
* /
2018-08-01 22:56:01 +02:00
private HuamiSupport sendCustomNotification ( VibrationProfile vibrationProfile , SimpleNotification simpleNotification , int flashTimes , int flashColour , int originalColour , long flashDuration , BtLEAction extraAction , TransactionBuilder builder ) {
2017-03-02 00:27:54 +01:00
getNotificationStrategy ( ) . sendCustomNotification ( vibrationProfile , simpleNotification , flashTimes , flashColour , originalColour , flashDuration , extraAction , builder ) ;
2016-07-25 00:00:22 +02:00
LOG . info ( " Sending notification to MiBand " ) ;
return this ;
}
2017-08-13 16:31:11 +02:00
public NotificationStrategy getNotificationStrategy ( ) {
2017-03-15 00:26:39 +01:00
String firmwareVersion = getDevice ( ) . getFirmwareVersion ( ) ;
if ( firmwareVersion ! = null ) {
Version ver = new Version ( firmwareVersion ) ;
if ( MiBandConst . MI2_FW_VERSION_MIN_TEXT_NOTIFICATIONS . compareTo ( ver ) > 0 ) {
return new Mi2NotificationStrategy ( this ) ;
}
}
if ( GBApplication . getPrefs ( ) . getBoolean ( MiBandConst . PREF_MI2_ENABLE_TEXT_NOTIFICATIONS , true ) ) {
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 ) {
2017-03-11 11:29:50 +01:00
int fitnessGoal = GBApplication . getPrefs ( ) . getInt ( ActivityUser . PREF_USER_STEPS_GOAL , 10000 ) ;
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
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 ) ;
2016-11-13 01:42:55 +01:00
int location = MiBandCoordinator . getWearLocation ( getDevice ( ) . getAddress ( ) ) ;
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 ) {
2016-11-13 01:42:55 +01:00
final boolean enableHrSleepSupport = MiBandCoordinator . getHeartrateSleepSupport ( getDevice ( ) . getAddress ( ) ) ;
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 ) ;
}
}
2017-08-18 16:21:54 +02:00
protected void performPreferredNotification ( String task , String notificationOrigin , SimpleNotification simpleNotification , int alertLevel , BtLEAction extraAction ) {
2016-07-25 00:00:22 +02:00
try {
TransactionBuilder builder = performInitialized ( task ) ;
Prefs prefs = GBApplication . getPrefs ( ) ;
int vibrateDuration = getPreferredVibrateDuration ( notificationOrigin , prefs ) ;
int vibratePause = getPreferredVibratePause ( notificationOrigin , prefs ) ;
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
int flashTimes = getPreferredFlashCount ( notificationOrigin , prefs ) ;
int flashColour = getPreferredFlashColour ( notificationOrigin , prefs ) ;
int originalColour = getPreferredOriginalColour ( notificationOrigin , prefs ) ;
int flashDuration = getPreferredFlashDuration ( notificationOrigin , prefs ) ;
2017-03-02 00:27:54 +01:00
sendCustomNotification ( profile , simpleNotification , flashTimes , flashColour , originalColour , flashDuration , extraAction , builder ) ;
2017-03-14 00:44:59 +01:00
2016-07-25 00:00:22 +02:00
// sendCustomNotification(vibrateDuration, vibrateTimes, vibratePause, flashTimes, flashColour, originalColour, flashDuration, builder);
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to send notification to MI device " , ex ) ;
}
}
private int getPreferredFlashDuration ( String notificationOrigin , Prefs prefs ) {
return getNotificationPrefIntValue ( FLASH_DURATION , notificationOrigin , prefs , DEFAULT_VALUE_FLASH_DURATION ) ;
}
private int getPreferredOriginalColour ( String notificationOrigin , Prefs prefs ) {
return getNotificationPrefIntValue ( FLASH_ORIGINAL_COLOUR , notificationOrigin , prefs , DEFAULT_VALUE_FLASH_ORIGINAL_COLOUR ) ;
}
private int getPreferredFlashColour ( String notificationOrigin , Prefs prefs ) {
return getNotificationPrefIntValue ( FLASH_COLOUR , notificationOrigin , prefs , DEFAULT_VALUE_FLASH_COLOUR ) ;
}
private int getPreferredFlashCount ( String notificationOrigin , Prefs prefs ) {
return getNotificationPrefIntValue ( FLASH_COUNT , notificationOrigin , prefs , DEFAULT_VALUE_FLASH_COUNT ) ;
}
private int getPreferredVibratePause ( String notificationOrigin , Prefs prefs ) {
return getNotificationPrefIntValue ( VIBRATION_PAUSE , notificationOrigin , prefs , DEFAULT_VALUE_VIBRATION_PAUSE ) ;
}
private short getPreferredVibrateCount ( String notificationOrigin , Prefs prefs ) {
return ( short ) Math . min ( Short . MAX_VALUE , getNotificationPrefIntValue ( VIBRATION_COUNT , notificationOrigin , prefs , DEFAULT_VALUE_VIBRATION_COUNT ) ) ;
}
private int getPreferredVibrateDuration ( String notificationOrigin , Prefs prefs ) {
return getNotificationPrefIntValue ( VIBRATION_DURATION , notificationOrigin , prefs , DEFAULT_VALUE_VIBRATION_DURATION ) ;
}
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 ) {
anyAlarmEnabled | = alarm . isEnabled ( ) ;
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 ) ;
}
}
@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 ;
AbortTransactionAction abortAction = new StopNotificationAction ( getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_ALERT_LEVEL ) ) {
@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.
sendCalendarEvents ( builder ) ;
2016-07-25 00:00:22 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to set time on MI device " , ex ) ;
}
}
@Override
public void onSetCallState ( CallSpec callSpec ) {
if ( callSpec . command = = CallSpec . CALL_INCOMING ) {
telephoneRinging = true ;
2017-02-07 23:49:10 +01:00
AbortTransactionAction abortAction = new StopNotificationAction ( getCharacteristic ( GattCharacteristic . 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 ;
2017-03-14 00:44:59 +01:00
stopCurrentNotification ( ) ;
}
}
private void stopCurrentNotification ( ) {
try {
TransactionBuilder builder = performInitialized ( " stop notification " ) ;
getNotificationStrategy ( ) . stopCurrentNotification ( builder ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
LOG . error ( " Error stopping 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 ;
}
if ( bufferMusicStateSpec ! = stateSpec ) {
2018-08-06 23:11:40 +02:00
bufferMusicStateSpec = stateSpec ;
2018-08-08 17:48:23 +02:00
sendMusicStateToDevice ( ) ;
2018-08-07 12:44:00 +02:00
}
2018-08-06 23:11:40 +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 ;
}
if ( bufferMusicSpec ! = musicSpec ) {
2018-08-06 23:11:40 +02:00
bufferMusicSpec = musicSpec ;
2018-08-08 17:48:23 +02:00
if ( isMusicAppStarted ) {
sendMusicStateToDevice ( ) ;
}
2018-08-07 12:44:00 +02:00
}
2018-08-06 23:11:40 +02:00
}
private void sendMusicStateToDevice ( ) {
if ( characteristicChunked = = null ) {
return ;
}
2018-08-17 00:19:10 +02:00
if ( bufferMusicSpec = = null | | bufferMusicStateSpec = = null ) {
2018-08-06 23:11:40 +02:00
try {
TransactionBuilder builder = performInitialized ( " send dummy playback info to enable music controls " ) ;
writeToChunked ( builder , 3 , new byte [ ] { 1 , 0 , 1 , 0 , 0 , 0 , 1 , 0 } ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
LOG . error ( " Unable to send dummy music controls " ) ;
}
return ;
}
byte flags = 0x00 ;
flags | = 0x01 ;
int length = 8 ;
if ( bufferMusicSpec . track ! = null & & bufferMusicSpec . track . getBytes ( ) . length > 0 ) {
length + = bufferMusicSpec . track . getBytes ( ) . length + 1 ;
flags | = 0x02 ;
}
if ( bufferMusicSpec . album ! = null & & bufferMusicSpec . album . getBytes ( ) . length > 0 ) {
length + = bufferMusicSpec . album . getBytes ( ) . length + 1 ;
flags | = 0x04 ;
}
if ( bufferMusicSpec . artist ! = null & & bufferMusicSpec . artist . getBytes ( ) . length > 0 ) {
length + = bufferMusicSpec . artist . getBytes ( ) . length + 1 ;
flags | = 0x08 ;
}
// LOG.info("Music flags are: " + (flags & 0xff));
try {
ByteBuffer buf = ByteBuffer . allocate ( length ) ;
buf . order ( ByteOrder . LITTLE_ENDIAN ) ;
buf . put ( flags ) ;
byte state ;
switch ( bufferMusicStateSpec . state ) {
case MusicStateSpec . STATE_PLAYING :
state = 1 ;
break ;
default :
state = 0 ;
}
buf . put ( state ) ;
buf . put ( new byte [ ] { 0x1 , 0x0 , 0x0 , 0x0 } ) ; //unknown
buf . put ( new byte [ ] { 0x1 , 0x0 } ) ; //show track
// buf.put(new byte[]{0x1,0x1}); //show album
if ( bufferMusicSpec . track ! = null & & bufferMusicSpec . track . getBytes ( ) . length > 0 ) {
buf . put ( bufferMusicSpec . track . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
}
if ( bufferMusicSpec . album ! = null & & bufferMusicSpec . album . getBytes ( ) . length > 0 ) {
buf . put ( bufferMusicSpec . album . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
}
if ( bufferMusicSpec . artist ! = null & & bufferMusicSpec . artist . getBytes ( ) . length > 0 ) {
buf . put ( bufferMusicSpec . artist . getBytes ( ) ) ;
buf . put ( ( byte ) 0 ) ;
}
TransactionBuilder builder = performInitialized ( " send playback info " ) ;
writeToChunked ( builder , 3 , buf . array ( ) ) ;
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
LOG . error ( " Unable to send playback state " ) ;
}
// LOG.info("Sent music: " + bufferMusicSpec.toString() + " " + bufferMusicStateSpec.toString());
2016-07-25 00:00:22 +02:00
}
@Override
public void onReboot ( ) {
try {
TransactionBuilder builder = performInitialized ( " Reboot " ) ;
2017-09-25 00:03:40 +02:00
sendReboot ( builder ) ;
2016-07-25 00:00:22 +02:00
builder . queue ( getQueue ( ) ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to reboot MI " , ex ) ;
}
}
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 ;
}
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 " ) ;
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 ) {
2017-10-23 10:28:54 +02:00
LOG . error ( " Unable to read heart rate with MI2 " , 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 " ) ;
2017-11-11 23:36:38 +01:00
BluetoothGattCharacteristic heartrateCharacteristic = getCharacteristic ( GattCharacteristic . UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT ) ;
if ( heartrateCharacteristic ! = null ) {
builder . notify ( heartrateCharacteristic , enable ) ;
}
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
}
@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 ) {
LOG . error ( " Unable to fetch MI activity data " , ex ) ;
}
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 ) {
byte result [ ] = new byte [ 12 ] ;
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 {
new UpdateFirmwareOperation ( uri , this ) . perform ( ) ;
} 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
}
2017-09-10 21:11:50 +02:00
public void runButtonAction ( ) {
Prefs prefs = GBApplication . getPrefs ( ) ;
if ( currentButtonTimerActivationTime ! = currentButtonPressTime ) {
return ;
}
String requiredButtonPressMessage = prefs . getString ( MiBandConst . PREF_MIBAND_BUTTON_PRESS_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 ) ;
if ( prefs . getBoolean ( MiBandConst . PREF_MIBAND_BUTTON_ACTION_VIBRATE , false ) ) {
2018-08-02 10:55:30 +02:00
performPreferredNotification ( null , null , null , HuamiService . ALERT_LEVEL_VIBRATE_ONLY , null ) ;
2017-09-10 21:11:50 +02:00
}
currentButtonActionId = 0 ;
currentButtonPressCount = 0 ;
currentButtonPressTime = System . currentTimeMillis ( ) ;
}
2017-10-21 22:50:28 +02:00
public 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 :
callCmd . event = GBDeviceEventCallControl . Event . REJECT ;
evaluateGBDeviceEvent ( callCmd ) ;
break ;
2017-11-19 23:46:24 +01:00
case HuamiDeviceEvent . CALL_IGNORE :
LOG . info ( " ignore call (not yet supported) " ) ;
//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 " ) ;
break ;
case HuamiDeviceEvent . START_NONWEAR :
LOG . info ( " non-wear start detected " ) ;
break ;
case HuamiDeviceEvent . ALARM_TOGGLED :
LOG . info ( " An alarm was toggled " ) ;
break ;
case HuamiDeviceEvent . FELL_ASLEEP :
LOG . info ( " Fell asleep " ) ;
break ;
case HuamiDeviceEvent . WOKE_UP :
LOG . info ( " Woke up " ) ;
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 ;
2017-10-21 22:50:28 +02:00
default :
LOG . warn ( " unhandled event " + value [ 0 ] ) ;
}
}
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 ) ;
}
}
2017-10-21 22:50:28 +02:00
public void handleButtonEvent ( ) {
2017-09-10 21:11:50 +02:00
///logMessageContent(value);
// If disabled we return from function immediately
Prefs prefs = GBApplication . getPrefs ( ) ;
if ( ! prefs . getBoolean ( MiBandConst . PREF_MIBAND_BUTTON_ACTION_ENABLE , false ) ) {
return ;
}
int buttonPressMaxDelay = prefs . getInt ( MiBandConst . PREF_MIBAND_BUTTON_PRESS_MAX_DELAY , 2000 ) ;
int buttonActionDelay = prefs . getInt ( MiBandConst . PREF_MIBAND_BUTTON_ACTION_DELAY , 0 ) ;
int requiredButtonPressCount = prefs . getInt ( MiBandConst . PREF_MIBAND_BUTTON_PRESS_COUNT , 0 ) ;
if ( requiredButtonPressCount > 0 ) {
long timeSinceLastPress = System . currentTimeMillis ( ) - currentButtonPressTime ;
if ( ( currentButtonPressTime = = 0 ) | | ( timeSinceLastPress < buttonPressMaxDelay ) ) {
currentButtonPressCount + + ;
}
else {
currentButtonPressCount = 1 ;
currentButtonActionId = 0 ;
}
currentButtonPressTime = System . currentTimeMillis ( ) ;
if ( currentButtonPressCount = = requiredButtonPressCount ) {
currentButtonTimerActivationTime = currentButtonPressTime ;
if ( buttonActionDelay > 0 ) {
LOG . info ( " Activating timer " ) ;
final Timer buttonActionTimer = new Timer ( " Mi Band Button Action Timer " ) ;
buttonActionTimer . scheduleAtFixedRate ( new TimerTask ( ) {
@Override
public void run ( ) {
runButtonAction ( ) ;
buttonActionTimer . cancel ( ) ;
}
} , buttonActionDelay , buttonActionDelay ) ;
}
else {
LOG . info ( " Activating button action " ) ;
runButtonAction ( ) ;
}
currentButtonActionId + + ;
currentButtonPressCount = 0 ;
}
}
}
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 ;
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 ( ) ;
2016-10-30 23:04:21 +01:00
if ( GattCharacteristic . UUID_CHARACTERISTIC_GAP_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
}
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 ( ) ;
Device device = DBHelper . getDevice ( getDevice ( ) , session ) ;
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 ) {
2016-10-21 00:49:42 +02:00
Calendar calendar = alarm . getAlarmCal ( ) ;
2016-10-23 23:05:54 +02:00
int maxAlarms = 5 ; // arbitrary at the moment...
if ( alarm . getIndex ( ) > = maxAlarms ) {
2016-10-23 22:37:18 +02:00
if ( alarm . isEnabled ( ) ) {
2016-10-23 23:05:54 +02:00
GB . toast ( getContext ( ) , " Only 5 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 ;
}
2016-10-28 23:18:10 +02:00
int base = 0 ;
if ( alarm . isEnabled ( ) ) {
base = 128 ;
}
2016-10-28 22:25:38 +02:00
int daysMask = alarm . getRepetitionMask ( ) ;
2016-10-28 23:18:10 +02:00
if ( ! alarm . isRepetitive ( ) ) {
daysMask = 128 ;
}
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?
2016-10-28 22:25:38 +02:00
( byte ) ( base + alarm . getIndex ( ) ) , // 128 is the base, alarm slot is added
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
}
private void handleDeviceInfo ( nodomain . freeyourgadget . gadgetbridge . service . btle . profiles . deviceinfo . DeviceInfo info ) {
// 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
2016-12-01 22:49:58 +01:00
Prefs prefs = GBApplication . getPrefs ( ) ;
int availableSlots = prefs . getInt ( MiBandConst . PREF_MIBAND_RESERVE_ALARM_FOR_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 ;
for ( CalendarEvents . CalendarEvent mEvt : mEvents ) {
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 ( ) ) ;
Alarm alarm = GBAlarm . createSingleShot ( slotToUse , false , calendar ) ;
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
}
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 ;
2018-03-23 23:27:03 +01:00
case MiBandConst . PREF_ACTIVATE_DISPLAY_ON_LIFT :
case MiBandConst . PREF_DISPLAY_ON_LIFT_START :
case MiBandConst . PREF_DISPLAY_ON_LIFT_END :
2016-11-13 21:33:43 +01:00
setActivateDisplayOnLiftWrist ( builder ) ;
break ;
2017-07-09 16:17:13 +02:00
case MiBandConst . PREF_MI2_DISPLAY_ITEMS :
setDisplayItems ( 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 ;
2017-07-15 15:01:07 +02:00
case MiBandConst . PREF_MI2_DO_NOT_DISTURB :
case MiBandConst . PREF_MI2_DO_NOT_DISTURB_START :
case MiBandConst . PREF_MI2_DO_NOT_DISTURB_END :
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 ;
2016-11-13 20:47:24 +01:00
}
builder . queue ( getQueue ( ) ) ;
} catch ( IOException e ) {
GB . toast ( " Error setting configuration " , Toast . LENGTH_LONG , GB . ERROR , e ) ;
}
}
2016-10-11 23:06:59 +02:00
@Override
public void onTestNewFunction ( ) {
2017-02-21 21:41:21 +01:00
try {
2017-10-19 21:52:38 +02:00
new FetchSportsSummaryOperation ( this ) . perform ( ) ;
} catch ( IOException ex ) {
LOG . error ( " Unable to fetch MI activity data " , ex ) ;
2017-02-21 21:41:21 +01:00
}
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 ) {
}
2018-08-01 22:56:01 +02:00
private HuamiSupport setDateDisplay ( TransactionBuilder builder ) {
2017-10-23 14:46:08 +02:00
DateTimeDisplay dateTimeDisplay = HuamiCoordinator . getDateDisplay ( getContext ( ) ) ;
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 ;
}
2018-08-01 22:56:01 +02:00
private HuamiSupport setTimeFormat ( TransactionBuilder builder ) {
2017-03-03 22:32:54 +01:00
boolean is24Format = DateFormat . is24HourFormat ( getContext ( ) ) ;
LOG . info ( " Setting 24h time format to " + is24Format ) ;
if ( is24Format ) {
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 ) {
2018-03-23 23:27:03 +01:00
ActivateDisplayOnLift displayOnLift = HuamiCoordinator . getActivateDisplayOnLiftWrist ( getContext ( ) ) ;
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 ( ) ;
Date start = HuamiCoordinator . getDisplayOnLiftStart ( ) ;
calendar . setTime ( start ) ;
cmd [ 4 ] = ( byte ) calendar . get ( Calendar . HOUR_OF_DAY ) ;
cmd [ 5 ] = ( byte ) calendar . get ( Calendar . MINUTE ) ;
Date end = HuamiCoordinator . getDisplayOnLiftEnd ( ) ;
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 ) {
2017-10-23 14:46:08 +02:00
Set < String > pages = HuamiCoordinator . getDisplayItems ( ) ;
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
}
2017-07-09 16:17:13 +02:00
}
2018-08-02 10:55:30 +02:00
builder . write ( getCharacteristic ( HuamiService . UUID_CHARACTERISTIC_3_CONFIGURATION ) , data ) ;
2017-07-13 23:26:25 +02:00
return this ;
}
2017-07-09 16:17:13 +02:00
2018-08-01 22:56:01 +02:00
private HuamiSupport setRotateWristToSwitchInfo ( TransactionBuilder builder ) {
2017-10-23 14:46:08 +02:00
boolean enable = HuamiCoordinator . getRotateWristToSwitchInfo ( ) ;
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 ) {
2017-10-23 14:46:08 +02:00
DoNotDisturb doNotDisturb = HuamiCoordinator . getDoNotDisturb ( getContext ( ) ) ;
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 ( ) ;
2017-10-23 14:46:08 +02:00
Date start = HuamiCoordinator . getDoNotDisturbStart ( ) ;
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
2017-10-23 14:46:08 +02:00
Date end = HuamiCoordinator . getDoNotDisturbEnd ( ) ;
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 ;
}
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 ;
}
2018-07-21 17:18:08 +02:00
protected void writeToChunked ( TransactionBuilder builder , int type , byte [ ] data ) {
final int MAX_CHUNKLENGTH = 17 ;
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 ;
}
}
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 ) ;
2017-11-11 00:04:51 +01:00
setHeartrateMeasurementInterval ( builder , getHeartRateMeasurementInterval ( ) ) ;
}
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 ) ;
}
2016-07-25 00:00:22 +02:00
}